Universal Fix Pattern ====================== .. contents:: :local: Overview -------- Every CWE-407 graph traversal defect in this survey follows the same pattern and admits the same fix. The defect is a list used for visited/stack state with linear-scan membership. The fix is a hash-backed set with O(1) membership. The fix pattern is one-to-one: one line of type change, one semantic-equivalent operation change. The table below gives the canonical before/after for every affected language. Fix Pattern Table ----------------- Java ~~~~ .. code-block:: java // DEFECTIVE List visited = new ArrayList<>(); if (visited.contains(x)) { /* already seen */ } visited.add(x); // FIXED Set visited = new HashSet<>(); if (!visited.add(x)) { /* already seen — add() returns false for duplicates */ } // ALSO FIXED (separate contains + add) Set visited = new HashSet<>(); if (visited.contains(x)) { /* already seen */ } visited.add(x); // ALSO FIXED (containsAll → Set.equals) // DEFECTIVE: if (boundsA.containsAll(boundsB) && boundsB.containsAll(boundsA)) ... // FIXED: if (new HashSet<>(boundsA).equals(new HashSet<>(boundsB))) ... **Type change:** ``List`` → ``Set`` (``HashSet`` or ``LinkedHashSet`` for order-preserving) **Required traits/imports:** ``java.util.HashSet``; type ``T`` must implement ``equals()`` and ``hashCode()`` Python ~~~~~~ .. code-block:: python # DEFECTIVE visited = [] if x in visited: # O(n) pass visited.append(x) # FIXED visited = set() if x in visited: # O(1) pass visited.add(x) # ALSO FIXED (combined check-and-add) visited = set() if not visited.add(x): # Python: set.add returns None, so use: pass # OR: visited = set() newly_added = x not in visited visited.add(x) **Type change:** ``list`` → ``set`` **Required:** type ``x`` must be hashable (all built-in Python types are; custom classes need ``__hash__``) Haskell ~~~~~~~ .. code-block:: haskell -- DEFECTIVE import Data.List (elem) dfs visited v | v `elem` visited = ... -- O(n) -- FIXED with Data.Set (O(log n)) import qualified Data.Set as Set dfs visited v | Set.member v visited = ... -- O(log n) -- FIXED with Data.HashMap (O(1)) import qualified Data.HashMap.Strict as HashMap dfs visited v | HashMap.member v visited = ... -- O(1) **Type change:** ``[a]`` → ``Set a`` (requires ``Ord a``) or ``HashMap a ()`` (requires ``Hashable a``, ``Eq a``) **Note:** ``Data.IntSet`` and ``Data.IntMap`` provide optimized O(min(n,W)) (W=word size) operations for integer node IDs — often faster than general ``HashMap`` for compiler node indices. Erlang ~~~~~~ .. code-block:: erlang %% DEFECTIVE case lists:member(V, Visited) of %% O(n) true -> ...; false -> dfs(G, [V | Visited], ...) end. %% FIXED with maps (O(1)) case maps:is_key(V, Visited) of %% O(1) amortized true -> ...; false -> dfs(G, Visited#{V => true}, ...) end. %% FIXED with gb_sets (O(log n)) case gb_sets:is_member(V, Visited) of %% O(log n) true -> ...; false -> dfs(G, gb_sets:add(V, Visited), ...) end. **Type change:** ``[term()]`` → ``map()`` (``#{}``) or ``gb_sets:set()`` **Required:** for maps, key type must be any Erlang term (maps support arbitrary keys); no constraint TypeScript ~~~~~~~~~~ .. code-block:: typescript // DEFECTIVE — pushIfUnique pattern const visited: Symbol[] = []; function pushIfUnique(array: T[], toAdd: T): boolean { if (array.includes(toAdd)) return false; // O(n) array.push(toAdd); return true; } // FIXED const visited = new Set(); function tryAdd(set: Set, toAdd: T): boolean { if (set.has(toAdd)) return false; // O(1) set.add(toAdd); return true; } // Or inline: if (!visited.has(sym)) { visited.add(sym); ... } // DEFECTIVE — Array.includes cycle check const resolutionTargets: object[] = []; if (resolutionTargets.includes(target)) ... // O(depth) // FIXED const resolutionTargets = new Set(); if (resolutionTargets.has(target)) ... // O(1) **Type change:** ``T[]`` → ``Set`` **Required:** ES2015 / TypeScript 2.0+; ``Set`` uses reference equality for objects (correct for node identity) Kotlin ~~~~~~ .. code-block:: kotlin // DEFECTIVE val visited = mutableListOf() if (descriptor in visited) return // O(n) — List.contains() // FIXED val visited = HashSet() if (!visited.add(descriptor)) return // O(1) — HashSet.add() returns false for duplicates // ALSO FIXED (explicit contains) val visited = HashSet() if (descriptor in visited) return // O(1) — HashSet.contains() via 'in' operator **Type change:** ``MutableList`` → ``HashSet`` **Note:** The ``in`` operator in Kotlin is polymorphic — it calls ``contains()`` on whatever collection type is used. The fix requires only a type change; the ``in`` syntax is identical. Scala ~~~~~ .. code-block:: scala // DEFECTIVE val visited = List[TypeParamRef]() if (visited.contains(tp)) ... // O(n) minLower.exists(p => minUpper.contains(p)) // O(n²) // FIXED val visited = Set[TypeParamRef]() if (visited.contains(tp)) ... // O(log n) with TreeSet, O(1) with HashSet // FIXED for the contains-in-exists pattern val upperSet = minUpper.toSet // O(n) once minLower.exists(p => upperSet.contains(p)) // O(n) total **Type change:** ``List[T]`` → ``Set[T]`` (``scala.collection.immutable.Set`` or ``mutable.HashSet``) **Note:** For the O(n³) scala3-0001 defect, precomputing the set at constraint construction time (not per-call) would reduce the total cost to O(n). Rust ~~~~ .. code-block:: rust // DEFECTIVE let mut visited: Vec> = Vec::new(); if visited.contains(&ty) { return; } // O(n) visited.push(ty); // FIXED use rustc_data_structures::fx::FxHashSet; let mut visited: FxHashSet> = FxHashSet::default(); if !visited.insert(ty) { return; } // O(1) // ALSO FIXED (SmallVec → FxHashSet) // DEFECTIVE: let mut stack: SmallVec<[Ty; 4]> = SmallVec::new(); if stack.contains(&self) { return; } // FIXED: let mut stack: FxHashSet = FxHashSet::default(); if !stack.insert(self) { return; } **Type change:** ``Vec`` or ``SmallVec<[T; N]>`` → ``FxHashSet`` (or ``HashSet`` from std) **Required trait bounds:** ``T: Hash + Eq`` (in addition to the existing ``T: PartialEq`` for ``Vec::contains``) C/C++ ~~~~~ .. code-block:: cpp // DEFECTIVE std::vector visited; if (std::find(visited.begin(), visited.end(), node) != visited.end()) { return; // O(n) } visited.push_back(node); // FIXED std::unordered_set visited; if (!visited.insert(node).second) { return; // O(1) — insert().second is false if already present } // ALSO FIXED (LLVM SmallPtrSet) llvm::SmallPtrSet visited; if (!visited.insert(node).second) { return; } // ALSO FIXED (keep ordering for stack output, add set for membership) std::vector stack; // for ordered pop std::unordered_set onStack; // for O(1) membership **Type change:** ``std::vector`` + ``std::find`` → ``std::unordered_set`` + ``insert/count`` **Required:** pointer types hash by address (default ``std::hash``); value types require custom hash Perl ~~~~ .. code-block:: perl # DEFECTIVE my @visited; if (grep { $_ eq $node } @visited) { return; } # O(n) push @visited, $node; # FIXED my %visited; if (exists $visited{$node}) { return; } # O(1) $visited{$node} = 1; **Type change:** ``@array`` + ``grep`` → ``%hash`` + ``exists`` Summary Table ------------- .. list-table:: :header-rows: 1 :widths: 12 25 25 18 20 * - Language - Defective - Fixed - Cost before - Cost after * - Java - ``List.contains(x)`` - ``Set.add(x)`` or ``Set.contains(x)`` - O(n) - O(1) * - Python - ``x in list`` - ``x in set`` - O(n) - O(1) * - Haskell - ``x \`elem\` list`` - ``Set.member x set`` - O(n) - O(log n) * - Erlang - ``lists:member(x, list)`` - ``maps:is_key(x, map)`` - O(n) - O(1) * - TypeScript - ``array.includes(x)`` - ``Set.has(x)`` - O(n) - O(1) * - Kotlin - ``x in list`` - ``x in hashSet`` - O(n) - O(1) * - Scala - ``list.contains(x)`` - ``set.contains(x)`` - O(n) - O(log n) * - Rust - ``vec.contains(&x)`` - ``HashSet::insert(&x)`` - O(n) - O(1) * - C/C++ - ``std::find(v.begin(), v.end(), x)`` - ``unordered_set::insert(x)`` - O(n) - O(1) * - Perl - ``grep { $_ eq $x } @array`` - ``exists $hash{$x}`` - O(n) - O(1)