CPython PEG Parser Generator — CWE-407 Analysis ================================================ .. contents:: :local: Overview -------- CPython's PEG parser generator (``peg_generator``) generates the parser used by CPython itself since Python 3.9. It includes a utility module ``sccutils.py`` that computes strongly connected components of the grammar rule dependency graph. One CWE-407 defect site was found in the SCC implementation. It has been patched. Defect Sites ------------ cpython-0001 ~~~~~~~~~~~~ **File:** ``Tools/peg_generator/peg_parser/sccutils.py:73`` **Pattern:** .. code-block:: python # Tarjan SCC — 'node in path' where path is a list def strongly_connected_components(graph): identified = set() stack = [] lowlinks = {} index = {} def dfs(v): index[v] = lowlinks[v] = len(index) stack.append(v) for w in graph.get(v, ()): if w not in index: dfs(w) lowlinks[v] = min(lowlinks[v], lowlinks[w]) elif w not in identified: if w in stack: # O(V) — 'in' on list is linear scan lowlinks[v] = min(lowlinks[v], index[w]) if lowlinks[v] == index[v]: scc = set() while True: w = stack.pop() identified.add(w) scc.add(w) if w == v: break yield scc **Why this is O(n):** ``w in stack`` where ``stack`` is a Python list; ``in`` on a list is O(|stack|) = O(V). **Complexity:** ``O(V²)`` where V = number of grammar rules **Patch:** .. code-block:: python def strongly_connected_components(graph): identified = set() stack = [] on_stack = set() # O(1) membership test lowlinks = {} index = {} def dfs(v): index[v] = lowlinks[v] = len(index) stack.append(v) on_stack.add(v) for w in graph.get(v, ()): if w not in index: dfs(w) lowlinks[v] = min(lowlinks[v], lowlinks[w]) elif w not in identified: if w in on_stack: # O(1) lowlinks[v] = min(lowlinks[v], index[w]) if lowlinks[v] == index[v]: scc = set() while True: w = stack.pop() on_stack.discard(w) identified.add(w) scc.add(w) if w == v: break yield scc **Data structure change:** ``list`` + ``in`` → ``set`` + ``in`` (separate ``on_stack`` set) **Status:** Patched Benchmark Results ----------------- .. list-table:: :header-rows: 1 * - V (grammar rules) - Before (ops) - After (ops) - Ratio * - 100 - ~5,050 - ~100 - ~50x * - 500 - ~125,250 - ~500 - ~250x Complexity Proof ---------------- Let V = number of grammar rule nodes. ``w in stack`` where ``stack`` is a list of up to V elements costs O(V). Called once per edge in DFS: total O(V²) across all edges (in a dense graph). The patched code maintains a companion ``on_stack`` set — ``w in on_stack`` is O(1). Total: O(V + E) = O(V) for sparse grammars. QED. References ---------- * Defect ticket: ``tools/tickets/defects/cpython-0001.md`` * Patch: ``defects/cpython/patch/`` * Tests: ``defects/cpython/unit/``, ``defects/cpython/integration/``