java-topology/defects/pylons/patch/pylons-0001-toposorter-names-set.patch

48 lines
2 KiB
Diff

# UNDF: UNDF-2026-000000228
Fixes pylons-0001: TopologicalSorter.add()/sorted() — `if name in self.names` list scan O(N²) — CWE-407.
--- a/src/pyramid/util.py
+++ b/src/pyramid/util.py
@@ DEFECT pylons-0001: TopologicalSorter.__init__/add/remove/sorted lines 432, 449, 481, 577
class TopologicalSorter:
def __init__(self, default_before=LAST, default_after=None, first=FIRST, last=LAST):
self.names = []
+ self.names_set = set() # FIX pylons-0001: O(1) membership shadow set
self.req_before = set()
self.req_after = set()
...
def remove(self, name):
"""Remove a node from the sort input"""
- self.names.remove(name) # O(n) list scan — CWE-407
+ self.names_set.discard(name) # O(1) — fixed
+ self.names.remove(name) # O(n) but confirmed present; acceptable once
del self.name2val[name]
...
def add(self, name, val, after=None, before=None):
- if name in self.names: # O(n) list scan — CWE-407
+ if name in self.names_set: # O(1) set lookup — fixed
self.remove(name)
self.names.append(name)
+ self.names_set.add(name) # maintain shadow set
...
def sorted(self):
...
result = []
for name in sorted_names:
- if name in self.names: # O(n) list scan inside O(n) loop — CWE-407
+ if name in self.names_set: # O(1) set lookup — fixed
result.append((name, self.name2val[name]))
return result
# SUMMARY:
# add(): if name in self.names O(n) → O(1) via names_set
# sorted(): if name in self.names O(n) → O(1) via names_set [called in O(n) loop]
# remove(): self.names.remove() O(n) still (confirmed present; fires once per remove)
#
# Net: O(N²) → O(N) for add() sequence; O(N²) → O(N) for sorted() result loop.
# names list is preserved for iteration order (sorted() iterates self.names at line 507).