1.8 KiB
pylons-0001: TopologicalSorter.add() — O(N²) if name in self.names list scan
Severity: HIGH
File: src/pyramid/util.py (Pyramid web framework, Pylons project)
Line: 481
Status: PATCHED
Description
TopologicalSorter.add() maintains self.names as a plain list. Every call to
add() performs if name in self.names — an O(N) linear scan. During Pyramid
application startup the framework calls add() N times (once per tween, once per
view deriver, once per predicate): total O(N²) scans.
The same self.names list is scanned again in sorted() at line 577:
for name in sorted_names: # O(N) loop
if name in self.names: # O(N) list scan — CWE-407
That gives a second O(N²) pass on every call to sorted().
TopologicalSorter is used in four hot-path config callsites:
config/tweens.py:166— tween chain construction (every request lifecycle)config/views.py:117— Accept header orderingconfig/views.py:1315,1405— view deriver chainconfig/predicates.py:109— predicate ordering
Root Cause
self.names = [] at line 432. Python list.__contains__ is O(N); there is no
parallel set to give O(1) membership.
Fix
Maintain a parallel self.names_set = set() alongside self.names list.
add()line 481:if name in self.names_set:— O(1)sorted()line 577:if name in self.names_set:— O(1)remove()line 449: replaceself.names.remove(name)with indexed pop after O(1) set confirmation; updateself.names_set.discard(name).
See patch: defects/pylons/patch/pylons-0001-toposorter-names-set.patch
Speedup
N=1000 nodes (realistic large tween+deriver+predicate config):
- Slow: ~O(N²) = ~1,000,000 list element comparisons
- Fast: ~O(N) = ~1,000 set hash lookups
- Speedup: ~1000x at N=1000; scales quadratically vs linearly