java-topology/defects/cpython/patch/cpython-0002-turtle-methoddict-diamond-bases.md
russell@unturf.com 4d4c256673 diamond-scan: cpython/django O(2^D) hierarchy traversal; flask/rails/ruby/phoenix CLEAN
cpython-0001: pydoc.allmethods unconditional __bases__ recursion, no visited guard
cpython-0002: turtle.__methodDict unconditional __bases__ recursion, fires at import
cpython-0003: idlelib.rpc._getmethods unconditional __bases__ recursion, IDLE debug path
django-0006: migrations.state.flatten_bases abstract model diamond traversal (comment in source acknowledges duplicates)
flask/rails/ruby/phoenix: CLEAN
2026-03-29 20:16:12 -04:00

2.7 KiB
Raw Blame History

UNDF: (pending)

cpython-0002: turtle.__methodDict — 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 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.