Repository navigation
fix(protocols): raise RegistryError for a non-class in seven register guards (#1026) - #1028
Conversation
… guards (#1026) The `register` classmethods in `pcapkit/protocols/` document `RegistryError` for a bad argument but leaked a bare `TypeError` for a non-class, because the only check was `issubclass`, which rejects a non-class itself. Closes #1026. * Seven sites gain an explicit `isinstance(..., type)` test ahead of their `issubclass`, after the `ModuleDescriptor` unwrap where one applies: `ProtocolBase.register` (`protocol.py:769`), `Frame.register` (`frame.py:152`), `PCAPNG.register` (`pcapng.py:920`), `SCTP.register` (`sctp.py:629`), `Link.register` (`link.py:144`), `Internet.register` (`internet.py:164`) and the `Transport` guard reached through `TCP.register` and `UDP.register` (`transport.py:115`). * No guard's target class changes, so a wrong *class* raises `RegistryError` exactly as before. `Transport.register` itself is unaffected: its `UnsupportedCall` is gated on `cls is Transport`, so it still short-circuits for the abstract class at `transport.py:110`. That gate is also why the guard below it was reachable at all -- `TCP.register` and `UDP.register` are the only callers that get past it, and both leaked. Measured per site, reading the raise location off `traceback.extract_tb(e.__traceback__)[-1]` rather than off the message, because a plain-`type` metaclass and an `ABCMeta`-derived one produce the identical `issubclass() arg 1 must be a class` text from different frames: ProtocolBase.register -> RegistryError | protocol.py:769 Frame.register -> RegistryError | frame.py:152 PCAPNG.register -> RegistryError | pcapng.py:920 SCTP.register -> RegistryError | sctp.py:629 Link.register -> RegistryError | link.py:144 Internet.register -> RegistryError | internet.py:164 TCP.register -> RegistryError | transport.py:115 UDP.register -> RegistryError | transport.py:115 Transport.register -> UnsupportedCall | transport.py:110 Not breaking for exception handling: `RegistryError` subclasses `TypeError` through `BaseError`, so `except TypeError` still catches it. Two side effects do come with it, because `BaseError` is loud by default -- the non-class path now emits one `CRITICAL` log record and, outside devmode, installs `sys.excepthook` and `threading.excepthook`, neither of which the bare `TypeError` from `abc` did. Measured on both trees. That is already the wrong-class path's behaviour, so this makes the two consistent. New test pins every site in 6 methods carrying 72 subtests. Against main's guards 40 subtests fail and 32 pass; with the subtest-free `Transport` method that is 33 checks passing either way. Those are genuine guards rather than filler: a wrong class still raising (8), a descriptor naming a wrong class still falling through to the subclass check (8), a valid class and a valid descriptor still registering (16, the only thing proving the new isinstance test rejects nothing it should accept), and `Transport` still short-circuiting (1). tests/protocols/test_register_class_guard_unit.py + protocol base, registry and code-registration: 49 passed, 83 subtests. Dispatch: 24 passed, 87 subtests. tests/protocols/transport + link: 191 passed, 152 subtests. tests/project: 268 passed, 1 skipped, 864 subtests.
|
Cross-review verdict on 1. The "not breaking" claim was measurably false, and I verified it myself before accepting it.
So "only a caller matching the exact type or the old message text sees a difference" was wrong — a caller watching logs or hooks sees it without touching either. The entry now says so, and says the honest mitigating thing: this is already the wrong-class path's behaviour, so the change makes the two consistent rather than inventing a side effect. The review also checked this was not a formula inherited from elsewhere — the sentence is introduced here, with no precedent on 2. "6 pass either way" was wrong; it is 33. I conflated the method count with the pass-either-way count. Measured with a
32 passing subtests plus the subtest-free What it confirmed independently, re-deriving rather than accepting: all nine entry points at the exact lines claimed, for all four non-class arguments, with the raise site read off the traceback — and at base every failure pointing at Three things it raised that I am not acting on, with reasons. Resetting |
467eced to
0475f20
Compare
|
Cross-review verdict on The scope claim held, which is what let claims 1–4 and 6 carry across without redoing them: The part worth recording. My new wording says the side effects are "the wrong-class path's existing behaviour, so the fix makes the two consistent". That was my inference. The review measured it on a tree with It also checked the placement of the devmode qualifier, which I had not: On editing two files a test compares, which was the real risk of hand-amending the changelog pair: extracting the entry from each and normalising markup gives 1521 characters on both sides, identical, and One correction to something I wrote, not to the change: I said the entry and the commit message both now state 33. The entry carries no subtest count at all, which is right — subtest arithmetic belongs in the commit message and PR body, not in a user-facing changelog. Flagging it so nobody looks for a "33" in the entry and concludes the edit was dropped. The review agreed with all three of my non-actions and is not re-raising them: the |
`process.rst:104` pinned 159 entries while the file holds 161, so `tests/project` was failing on `main` again. * #1025, #1028 and #1030 each measured 159 against a 158-entry base, which was correct for each branch in isolation. Merging all three added three entries and left the pin two behind. * This is the second time the same collision has landed, after `51100da7e` fixed a two-way version of it. Measuring per branch is not sufficient -- the pin is only correct at the moment it is measured against the tree it will merge into, so it needs re-measuring at merge time or the check will keep going red whenever two changelog-touching branches land together. Measured rather than incremented: `grep -cE '^\* ' docs/source/changelog/1.5.0.rst` gives 161. tests/project: 268 passed, 1 skipped, 864 subtests passed. The failing test was test_the_page_pins_its_own_measured_numbers.
Please follow the guide below
make pylint,make mypy,make isort)tests/protocols/test_register_class_guard_unit.py, new here.make testitself was not run: the full suite reaches ~56 GB on this host and gets OOM-killed, so the selections below were run instead, narrowly and sequentially.docs/source/changelog/and regeneratedCHANGELOG.mdWhat is the purpose of your pull request?
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingchore— anything elseDescription of your pull request and other information
Closes #1026. The seven
registerclassmethods underpcapkit/protocols/documentRegistryErrorfor a bad argument but leaked a bareTypeErrorfor a non-class, becauseissubclassrejects a non-class itself before any guard of ours runs. This is theprotocols/half of the same defect #1025 fixes infoundation/.Each site gains an explicit
isinstance(..., type)test ahead of itsissubclass, placed after theModuleDescriptorunwrap where one applies. No guard's target class changes, so a wrong class still raisesRegistryErrorexactly as before.Measured per site, with the raise location read off
traceback.extract_tb(e.__traceback__)[-1]rather than off the message — a plain-typemetaclass and anABCMeta-derived one produce the identicalissubclass() arg 1 must be a classtext from different frames, so the message cannot distinguish them:ProtocolBase.registerRegistryErrorprotocol.py:769Frame.registerRegistryErrorframe.py:152PCAPNG.registerRegistryErrorpcapng.py:920SCTP.registerRegistryErrorsctp.py:629Link.registerRegistryErrorlink.py:144Internet.registerRegistryErrorinternet.py:164TCP.registerRegistryErrortransport.py:115UDP.registerRegistryErrortransport.py:115Transport.registerUnsupportedCalltransport.py:110Transportis the subtle one and the issue's own premise was wrong about it:Transport.register'sUnsupportedCallis gated oncls is Transport, so it fires only for the abstract class. That gate is precisely why the guard below it was reachable —TCP.registerandUDP.registerget past it, and both leaked. So this is seven leaking sites, not six plus an exempt one.Not breaking for exception handling.
RegistryErrorsubclassesTypeErrorthroughBaseError, soexcept TypeErrorstill catches it. Two side effects do come with it, becauseBaseErroris loud by default: the non-class path now emits oneCRITICALlog record and, outside devmode, installssys.excepthookandthreading.excepthook— neither of which the bareabcTypeErrordid. Measured on both trees. That is already the wrong-class path's behaviour, so this makes the two consistent.Verified: the new test has 6 methods carrying 72 subtests. Against
main's guards 40 subtests fail and 32 pass; with the subtest-freeTransportmethod that is 33 checks passing either way. Those are genuine guards, not filler: a wrong class still raising (8), a descriptor naming a wrong class still falling through to the subclass check (8), a valid class and valid descriptor still registering (16 — the only thing proving the newisinstancetest rejects nothing it should accept), andTransportstill short-circuiting (1).tests/protocols/test_register_class_guard_unit.pyplus protocol-base, registry and code-registration: 49 passed, 83 subtests. Dispatch: 24 passed, 87 subtests.tests/protocols/transport+link: 191 passed, 152 subtests.tests/project: 268 passed, 1 skipped, 864 subtests.