java-topology/defects/cpython/patch/cpython-0003-turtle-methoddict-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

2.8 KiB
Raw Blame History

UNDF: UNDF-2026-000000674

UNDF: (pending)

cpython-0003: turtle.__methodDict — O(2^D) diamond base traversal

CWE-407 — Algorithmic Complexity: O(2^D) diamond mixin/base traversal

Field Value
ID cpython-0003
Severity MEDIUM
Ecosystem cpython
Package turtle
File Lib/turtle.py
Lines 286293
Complexity O(2^D) on diamond inheritance hierarchies
Hot path Called at module import time via __forwardmethods(ScrolledCanvas, TK.Canvas, '_canvas')

Defect

# BEFORE (DEFECT) — O(2^D): no visited guard, unconditional recursion into __bases__
def __methodDict(cls, _dict):
    """helper function for Scrolled Canvas"""
    baseList = list(cls.__bases__)
    baseList.reverse()
    for _super in baseList:
        __methodDict(_super, _dict)   # unconditional — diamond re-traversal
    for key, value in cls.__dict__.items():
        if type(value) == types.FunctionType:
            _dict[key] = value

Called at module import time (line 426):

__forwardmethods(ScrolledCanvas, TK.Canvas, '_canvas')

__forwardmethods calls __methodDict(toClass, _dict_1) where toClass is TK.Canvas. If TK.Canvas participates in a diamond MRO (common in Tkinter/ttk widget hierarchies), the traversal visits shared ancestors exponentially many times.

Fix

# AFTER — O(N): pass visited set to prevent re-traversal of shared ancestors
def __methodDict(cls, _dict, _visited=None):
    """helper function for Scrolled Canvas"""
    if _visited is None:
        _visited = set()
    if cls in _visited:
        return
    _visited.add(cls)
    baseList = list(cls.__bases__)
    baseList.reverse()
    for _super in baseList:
        __methodDict(_super, _dict, _visited)
    for key, value in cls.__dict__.items():
        if type(value) == types.FunctionType:
            _dict[key] = value

Alternatively, use cls.__mro__ directly:

# SIMPLER FIX: iterate MRO (linearized, no duplicates)
def __methodDict(cls, _dict):
    """helper function for Scrolled Canvas"""
    for klass in reversed(cls.__mro__):
        for key, value in klass.__dict__.items():
            if type(value) == types.FunctionType:
                _dict[key] = value

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×

Notes

This fires at every import turtle since __forwardmethods is called unconditionally at module level. For typical Tkinter class hierarchies the depth is bounded, limiting practical impact — but ttk widgets add multiple inheritance layers that could trigger exponential behavior.