java-topology/defects/cpython/patch/cpython-0002-pydoc-allmethods-diamond-bases.md
russell@unturf.com 130e4fb159 undf: assign 671-680 to diamond-scan defects; 680 total assigned
New defects from diamond O(2^D) sweep:
- cpython-0002/0003/0004: pydoc.allmethods, turtle.__methodDict, idlelib.rpc._getmethods
- micronaut-0005/0006/0007: populateTypeHierarchy, populateTypeArgumentsForInterfaces, SuperclassAwareTypeVisitor
- quarkus-0004/0005: HierarchyDiscovery.discoverTypes, ConfigMappingUtils.collectInterfacesRec
- weld-0004/0005: HierarchyDiscovery.discoverTypes, Services.identifyServiceInterfaces
- rails-0019: Digestor#dependency_digest Array#include? O(N²)
- spring-0007: AnnotationsScanner.processClassHierarchy O(2^D)
- django-0007: migrations.state.flatten_bases O(2^D)
- typescript-0005: hasBaseType O(2^D)
- hibernate-validator-0003: ClassHierarchyHelper.getImplementedInterfaces O(2^D)
- swift-0001: QualifiedLookupRequest::evaluate protocol superclass O(2^D)
2026-03-29 20:27:09 -04:00

85 lines
2.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UNDF: UNDF-2026-000000673
# UNDF: (pending)
# cpython-0002: pydoc.allmethods — O(2^D) diamond base traversal
## CWE-407 — Algorithmic Complexity: O(2^D) diamond mixin/base traversal
| Field | Value |
|-------|-------|
| ID | cpython-0002 |
| Severity | MEDIUM |
| Ecosystem | cpython |
| Package | pydoc |
| File | `Lib/pydoc.py` |
| Lines | 232240 |
| Complexity | O(2^D) on diamond inheritance hierarchies |
| Hot path | `pydoc.allmethods(cl)` — collect all methods across inheritance tree |
## Defect
```python
# BEFORE (DEFECT) — O(2^D): no visited guard, unconditional recursion
def allmethods(cl):
methods = {}
for key, value in inspect.getmembers(cl, inspect.isroutine):
methods[key] = 1
for base in cl.__bases__:
methods.update(allmethods(base)) # all your base are belong to us
for key in methods.keys():
methods[key] = getattr(cl, key)
return methods
```
With a diamond hierarchy `D → B, C → A` (A shared), `allmethods(D)` recurses into both
`B` and `C`, each of which recurses into `A`, visiting it twice. At depth D the traversal
visits `2^D` nodes instead of the O(D) unique classes.
## Fix
```python
# AFTER — O(N): guard at entry prevents re-visiting shared ancestors
def allmethods(cl, _visited=None):
if _visited is None:
_visited = set()
if cl in _visited:
return {}
_visited.add(cl)
methods = {}
for key, value in inspect.getmembers(cl, inspect.isroutine):
methods[key] = 1
for base in cl.__bases__:
methods.update(allmethods(base, _visited))
for key in methods.keys():
methods[key] = getattr(cl, key)
return methods
```
Note: `inspect.getmro(cl)` already returns the linearized MRO and could replace the
recursive traversal entirely:
```python
# SIMPLER FIX: use MRO directly (already handles diamond)
def allmethods(cl):
methods = {}
for klass in reversed(inspect.getmro(cl)):
for key, value in inspect.getmembers(klass, inspect.isroutine):
methods[key] = 1
for key in methods.keys():
methods[key] = getattr(cl, key)
return methods
```
## Speedup
| Diamond depth (D) | Nodes visited (before) | Nodes visited (after) | Speedup |
|------------------|----------------------|----------------------|---------|
| 5 | 31 | 6 | 5× |
| 10 | 1,023 | 11 | 93× |
| 15 | 32,767 | 16 | 2,048× |
| 20 | 1,048,575 | 21 | 49,932× |
## Notes
`allmethods` is defined in `pydoc.py` but is no longer called from the main doc-rendering
paths (which use `__mro__` directly). However, it remains exported as part of the module's
public API and is available for external callers. The defect is present and latent.