java-topology/docs/tickets/phoenix-0002-router-scope-pipe-through-list-membership.md

2.1 KiB
Raw Blame History

phoenix-0002: CWE-407 — router scope pipe_through O(n²) duplicate detection via list scan

Field Value
ID phoenix-0002
Project phoenixframework/phoenix
Severity LOW (compile-time only)
Status OPEN
CWE CWE-407 (Algorithmic Complexity)
Found 2026-03-27

Location

lib/phoenix/router/scope.ex:125

def pipe_through(module, new_pipes) do
  new_pipes = List.wrap(new_pipes)
  %{pipes: pipes} = top = get_top(module)

  if pipe = Enum.find(new_pipes, &(&1 in pipes)) do   # <-- O(n*m) list scan
    raise ArgumentError, "duplicate pipe_through for #{inspect(pipe)}. ..."
  end

  put_top(module, %{top | pipes: pipes ++ new_pipes})  # <-- also O(n) append
end

Root Cause

The struct field pipes: [] (line 12) is a plain list. Enum.find(new_pipes, &(&1 in pipes)) iterates new_pipes (length m) and for each element does &1 in pipes — O(n) list membership scan. Total: O(n×m).

Additionally pipes ++ new_pipes is O(n) list concatenation, which over repeated pipe_through calls builds O(n²) total work.

Impact

Compile-time only. Router compilation runs once at startup (or code-reload). Routers with many pipelines accumulate O(P²) work where P is the total number of accumulated pipe names. For typical routers (P < 20) this is negligible in absolute time, but the pattern is wrong.

The pipes field should be a MapSet to make both the duplicate check and membership queries O(1).

Fix

Change pipes: field from [] to MapSet.new() and update all usages:

# defstruct — change default
pipes: MapSet.new(),

# pipe_through — O(1) duplicate check
if pipe = Enum.find(new_pipes, &MapSet.member?(pipes, &1)) do

# accumulation — O(1) put instead of O(n) append
put_top(module, %{top | pipes: Enum.reduce(new_pipes, pipes, &MapSet.put(&2, &1))})

Callers of top.pipes that iterate over them (e.g. Enum.each) are unaffected — MapSet implements Enumerable.

Patch

defects/phoenix/patch/phoenix-0002-router-scope-pipes-mapset.patch

Benchmark

defects/phoenix/unit/PhoenixTest.java