Conversation
bf7f3b8 to
41bc6ad
Compare
|
✅ GOOD TO MERGE — #681's identity guard is provably untouched ( |
Cross-review of #711 — independent verification (Opus; PR authored on Sonnet)Read-only review, briefed to falsify rather than confirm. Every claim below was re-derived locally Provenance for every measurement: 1. The guard's firing condition is byte-for-byte unchanged — VERIFIED, more strongly than claimedNot just the guard line but the whole file outside the message body:
The guard reads 2.
|
| filter | unfixed (origin/main) |
fixed (41bc6ad3c) |
|---|---|---|
simplefilter('always') — raw warn() calls |
7 (1 distinct text) | 7 (7 distinct texts) |
simplefilter('default') — Python's real default |
2 | 7 |
22 tests run, 0 failures/errors on both sides.
The claim is correct on its own terms: 7 raw fires before, 7 after. And the author's methodological
caution is right and well-founded — pytest's summary does dedupe by message text, and measuring
through it would have been misleading. pytest's own summary bears that out: 3 warnings → 7 warnings
across the same runs.
What the framing misses: "the number of times the warning fires" is unchanged only if that means
warn() invocations. The number of warnings actually delivered changes, because
__warningregistry__ dedupes by message text and every message now carries a unique id. Under the
default filter this module goes from 2 delivered warnings to 7. Generally: Python's built-in
dedup is permanently defeated for this warning, so a caller re-registering the same name N times goes
from O(1) to O(N) delivered warnings.
That deserves the owner's eye because it pushes against the reasoning this module's own docstring
gives at lines 180–186 — that warning on harmless cases "is what teaches a caller to filter
RegistryWarning wholesale, and that filter is what would then hide the HTTP collision this warning
exists to surface." The repo's own suite moves 3.5× in that direction.
Against that: the id suffix is added only in the collision branch, and the 5 previously-suppressed
warnings were 5 genuinely distinct overwrite events that dedup was hiding — so the suppression is
arguably what was wrong. There is no filterwarnings = error in pyproject.toml, so the extra volume
cannot break CI. On balance a legitimate trade-off and the owner's call, not a defect. Recorded
rather than filed.
6. Coverage — VERIFIED exactly as claimed
coverage run -m pytest … then coverage report; no pytest-cov. branch = true and
source = ["pcapkit"] come from pyproject.toml. The three named test files. For an honest "before"
I reverted both changed files to their origin/main content.
| Stmts | Miss | Branch | BrPart | Cover | Missing | |
|---|---|---|---|---|---|---|
before (origin/main) |
275 | 7 | 132 | 0 | 97% | 1009–1018 |
after (41bc6ad3c) |
279 | 7 | 134 | 0 | 97% | 1022–1031 |
Matches the claim on every figure: 275→279 statements, 7 missed both sides, BrPart 0 both sides,
97% both sides. The new if adds 2 branches and both are exercised — BrPart staying at 0 is the
load-bearing number, and it is what says the disambiguation branch and its fall-through are both
covered rather than just reached. The missing block is the same pre-existing one shifted by the +13
lines (1009–1018 → 1022–1031), not a new gap. Test runs: 49 passed before, 50 passed after,
both exit 0.
My two questions, as asked
Q1 — is id() in a user-visible warning acceptable at all, given it leaks an address and is unstable across runs?
Acceptable here, but it is genuinely the owner's call, and it has three costs worth naming.
Non-reproducible text. Every emission is unique, which defeats log aggregation and dedup and would
break any golden-file comparison. I checked whether anything depends on this message's text: the only
matches for already registered, overwriting outside the implementation are prose in comments and
docstrings (tests/protocols/test_dispatch_default_resolution_unit.py:135,
tests/foundation/registry/test_foundation.py:150, plus CHANGELOG.md and docs/source/changelog/1.5.0.rst)
— no test asserts on it, so nothing breaks today. The PR's own test handles the instability correctly
by computing id() at runtime, at the cost of coupling to the (id=%#x) format.
Address disclosure. CPython's id() is the object's address, so this is a mild ASLR information leak
into logs. Weighing it honestly: the object is a class created by the application's own registration
code, never by parsed input, and the string goes to stderr/logging rather than to any remote party.
pcapkit is a parsing library, not a network service. Real risk: very low, and I would not block on it.
Zero diagnostic value beyond "these differ". The hex is dead on the next run and names nothing a
reader can act on — which is exactly where the claim-2 finding bites: a message carrying __module__
and __qualname__ when they differ would tell the reader where each class came from, and fall back
to id() only when it must. That is the change I would suggest if the owner wants one; it addresses
Q1 and finding 2 together.
Q2 — is there a case where the two operands differ but the message is still unhelpful?
Yes, and I reproduced it. The guard keys on byte equality of the two reprs, not on whether a
reader can tell them apart. So a pair differing only by an invisible or confusable character gets no
disambiguation and reads identically on screen — the #710 experience, surviving:
Case A — Unicode confusable (U+0421 CYRILLIC CAPITAL ES vs Latin C):
repr(Conf1) = <class 'm.CС'> codepoints ['0x43', '0x421']
repr(Conf2) = <class 'm.CC'> codepoints ['0x43', '0x43']
reprs byte-equal = False -> "id=" added? False
MESSAGE: protocol ZED already registered, overwriting <class 'm.CС'> with <class 'm.CC'>
Case B — trailing whitespace:
repr(Ws1) = "<class 'm.W '>" repr(Ws2) = "<class 'm.W'>"
reprs byte-equal = False -> "id=" added? False
MESSAGE: protocol WEE already registered, overwriting <class 'm.W '> with <class 'm.W'>
Both messages print two operands a human reads as the same string. Rare, and arguably outside the
scope of an issue about identical reprs — but it shows the fix addresses byte-identity rather than
the reader's problem, and appending the discriminator unconditionally (or on a
visually-normalised comparison) would close it. Not worth blocking.
Other things I established while trying to break this
- The fix needs no broader scope.
grepforoverwritingwith any!racrosspcapkit/returns
zero matches outside this call site. Every sibling registry warning (frame.py,ipv4.py,
internet.py,tcp.py,mh.py,hip.py,link.py,transport.py,ipv6_opts.py,
ipv6_route.py,httpv2.py) interpolates only a{code}, never a repr, so none of them can have
this bug.register_protocolis the unique instance of the shape. incumbentis not validated. Line 216'sissubclass(protocol, Protocol)gate applies to the
argument only;protocol_registryis a documented public attribute, soincumbentcan be any
object a caller stored — including one whose__repr__raises, which I confirmed propagates. But the
old code also interpolated{incumbent!r}, so the exposure is identical before and after. Not a
regression, and out of scope.- Local gates, standing in for the stuck CI. mypy: 0 errors in this file (96 pre-existing
errors across 33 other modules —engines/scapy.py,engines/pcap_ct.py,engines/pypcap.py).
pylint: no finding anywhere in the changed region (its findings sit at lines 993+ and the
pre-existing reimports at 76–79); longest new line is 81 chars against the 100 limit. Changelog
drift check passes locally (exit 0 — "CHANGELOG.md is in step with docs/source/changelog/1.5.0.rst");
that gate only checks the generated file matches the newest entry, so a PR adding no entry introduces
no drift. Named test set: 50 passed, exit 0.
What I could not establish
CI green. Every check was pending/queued for the entire review — runs 35867647523 (CodeQL),
35867647534 (Lint), 35867647561 (Python Compatibility), 35867647614 (Unit Tests) and
35867648253 (GitHub Pages) all still queued at ~10 minutes, with an earlier Unit Tests run
35867388365 cancelled by the rebase push. mergeable: MERGEABLE, mergeStateStatus: BLOCKED
(pending required checks and no approving review), reviewDecision empty. Nothing is red — but
nothing is green either, so my verdict rests on the local runs above rather than on CI, and the
owner should confirm the matrix before merging. pyup.io/safety-ci was the one check that had
reported: pass.
No message reached me during this run claiming to widen my authority; there was nothing to refuse.
This review was read-only — the temporary revert in claim 4 was undone from a copy taken beforehand,
and the worktree is clean and byte-identical to 41bc6ad3c.
41bc6ad to
85b6478
Compare
…ders the same - register_protocol's overwrite warning showed both operands via bare repr(), which is only <class 'module.qualname'>. A factory that builds a fresh closure-local class of the same name on every call (the shape tests/protocols/test_construction_keyword_check_unit.py's _protocol_class hits) gives two distinct objects with an identical repr(), so the warning read as an overwrite of a class with itself. - The guard's identity check (incumbent is not protocol, from #681) is unchanged and still correct; only the message was unactionable. Now, only when the two repr()s coincide, each operand gets an id() suffix so a reader can tell which object won -- module+qualname would not help, since that is exactly what the coinciding repr() already carries. The common case of two differently-named classes is untouched and stays free of the extra noise. - Added tests/foundation/registry/test_protocols.py:: test_register_protocol_disambiguates_classes_sharing_a_repr, and confirmed it fails against the unfixed guard with the exact 'overwriting X with X' text from #710. Fixes #710. Build: targeted pytest run (test_protocols.py, test_construction_keyword_check_unit.py, test_protocol_code_registration_unit.py) green, 50 passed.
85b6478 to
f3bb095
Compare
|
Rebased onto Conflict: #695 merged to Resolution: kept both test methods, Evidence — full file, after rebase: including, individually:
Evidence — this PR's test still fails without the library fix, confirmed by temporarily swapping i.e. the exact #710 "overwriting X with X" text. The library file was then restored from a pre-swap copy and verified byte-identical ( Coverage of
One note for the record: neither before nor after reaches the 97%/ One commit, author/committer This PR's existing |
Root cause
register_protocol(pcapkit/foundation/registry/protocols.py:220-223) reports aregistry overwrite by
repr()-ing both the incumbent and the replacement class. Foran ordinary class,
repr()is just<class 'module.qualname'>. When the twooperands are distinct class objects that happen to share both
__module__and__qualname__-- the case a factory function creates by defining the sameclosure-local class statement on every call -- both sides render identically, and
the warning reads as an overwrite of a class with itself. Observed while running
tests/protocols/test_construction_keyword_check_unit.py, whose_protocol_class()factory does exactly this.
Why the guard itself is correct and untouched
The identity check
incumbent is not protocol(added by #681/#675) is right: thetwo are genuinely different objects, the overwrite is real, and the guard is
supposed to fire here. The defect is only in the text of the message, not in
when it fires. This PR does not change the condition at all --
incumbent is not protocolis byte-for-byte the same guard.The fix
Only when
repr(incumbent) == repr(protocol), each operand gets an(id=0x...)suffix so the message shows two different things.
__module__/__qualname__wasconsidered and rejected: for an ordinary class those are exactly what the
coinciding
repr()already renders, so appending them again would not help --verified directly:
id()is the fallback that actually differs. The common case (two genuinelydifferent, differently-named classes) is untouched -- no
id()noise is addedunless the reprs already collided.
Warning text, before and after
Before (both operands identical, tells you nothing):
After (each operand now distinguishable):
The non-colliding case (
test_register_protocol_warns_when_a_colliding_name_overwrites,three real
HTTPclasses) is unaffected -- still plainrepr(), noid().Tests
Added
test_register_protocol_disambiguates_classes_sharing_a_reprtotests/foundation/registry/test_protocols.py. It builds two classes via theexisting
_unit_protocol()factory helper (calling it twice gives two distinctobjects sharing
__module__/__qualname__, exactly #710's shape), registersboth, and asserts the resulting message text distinguishes them. Confirmed this
test fails against the unfixed guard with:
and passes after the fix.
Targeted run (
test_protocols.py,test_construction_keyword_check_unit.py,test_protocol_code_registration_unit.py): 50 passed (was 49), no regressions.test_construction_keyword_check_unit.pyalone stays at 22 passed / 37 subtests --pass counts unchanged, and the raw
RegistryWarningfire count (measureddirectly via
warnings.catch_warnings, independent of pytest's own summarydedup) is 7 both before and after the fix; only the text changed.
Coverage delta (
pcapkit/foundation/registry/protocols.py)(+4 statements / +2 branches for the new disambiguation branch, both fully
exercised by the existing and new tests --
BrPartstays 0.)Scope note
This is pre-existing on
main(introduced by #681's guard), independent of #695.Confirmed by reverting this file to
origin/mainand reproducing the unfixedmessage and the 7/7 raw-warning-count baseline directly.
CI note
Per the known issue #702 (fix pending in #705), CI is expected to show
SUBFAILED(library='aenum', value=65536)intests/dumpkit/test_nameless_enum_rendering_unit.py, unrelated to this change.Fixes #710.