game engines/web frameworks: 27 CWE-407 defects + 3 CLEAN; 194 sites, 78 ecosystems

This commit is contained in:
russell@unturf.com 2026-03-27 14:14:49 -04:00
parent 547a9f5738
commit 4d3fcc8e73
76 changed files with 6216 additions and 17 deletions

View file

@ -0,0 +1,29 @@
From 2db25de Phoenix HEAD (2026-03-27)
Subject: [PATCH phoenix-0001] Fix CWE-407: store event_intercepts as MapSet for O(1) dispatch lookup
The fastlane tuple stores event_intercepts as a plain list (the result of
channel.__intercepts__(), which returns @phoenix_intercepts — a list).
In dispatch/3, `event in event_intercepts` is called once per subscriber per
broadcast. With N subscribers and K intercepted events this is O(N*K) list
scans. Storing as MapSet at subscribe time makes the per-dispatch check O(1).
No change to the `event in event_intercepts` expression is needed — Elixir's
`in` operator dispatches to Enumerable.member?/2, which for MapSet is O(1).
CWE-407: Algorithmic Complexity (Inefficient Algorithmic Complexity)
---
lib/phoenix/channel/server.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/phoenix/channel/server.ex b/lib/phoenix/channel/server.ex
index xxxxxxx..yyyyyyy 100644
--- a/lib/phoenix/channel/server.ex
+++ b/lib/phoenix/channel/server.ex
@@ -440,7 +440,7 @@ defmodule Phoenix.Channel.Server do
Process.monitor(transport_pid)
- fastlane = {:fastlane, transport_pid, serializer, channel.__intercepts__()}
+ fastlane = {:fastlane, transport_pid, serializer, MapSet.new(channel.__intercepts__())}
PubSub.subscribe(pubsub_server, topic, metadata: fastlane)
{:noreply, %{socket | joined: true}}

View file

@ -0,0 +1,46 @@
From 2db25de Phoenix HEAD (2026-03-27)
Subject: [PATCH phoenix-0002] Fix CWE-407: use MapSet for router scope pipes accumulation
The Scope struct's `pipes` field is a plain list. `pipe_through/2` does
`Enum.find(new_pipes, &(&1 in pipes))` — O(n*m) — for duplicate detection,
then `pipes ++ new_pipes` — O(n) — for accumulation.
Change the `pipes` field default to `MapSet.new()`. Use `MapSet.member?/2`
for the duplicate check and `Enum.reduce/MapSet.put` for accumulation.
This is compile-time only so the practical impact is small, but the pattern
is wrong and MapSet is the correct data structure.
CWE-407: Algorithmic Complexity (Inefficient Algorithmic Complexity)
---
lib/phoenix/router/scope.ex | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/lib/phoenix/router/scope.ex b/lib/phoenix/router/scope.ex
index xxxxxxx..yyyyyyy 100644
--- a/lib/phoenix/router/scope.ex
+++ b/lib/phoenix/router/scope.ex
@@ -9,7 +9,7 @@ defmodule Phoenix.Router.Scope do
defstruct path: [],
alias: [],
as: [],
- pipes: [],
+ pipes: MapSet.new(),
hosts: [],
private: %{},
assigns: %{},
@@ -121,10 +121,12 @@ defmodule Phoenix.Router.Scope do
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
+ if pipe = Enum.find(new_pipes, &MapSet.member?(pipes, &1)) do
raise ArgumentError,
"duplicate pipe_through for #{inspect(pipe)}. " <>
"A plug may only be used once inside a scoped pipe_through"
end
- put_top(module, %{top | pipes: pipes ++ new_pipes})
+ put_top(module, %{top | pipes: Enum.reduce(new_pipes, pipes, &MapSet.put(&2, &1))})
end

View file

@ -0,0 +1,190 @@
package unit;
import java.util.*;
/**
* PhoenixTest — CWE-407 benchmark for Phoenix channel event_intercepts (phoenix-0001)
* and router scope pipes (phoenix-0002).
*
* Models the Elixir data structures in Java:
* - Slow: List<String> for event_intercepts / pipes (List.contains = O(n))
* - Fast: HashSet<String> for event_intercepts / pipes (HashSet.contains = O(1))
*
* phoenix-0001: dispatch() iterates all subscribers checking event in event_intercepts.
* Slow: O(subscribers * intercepts) list scan per broadcast.
* Fast: O(subscribers) with HashSet.contains(event).
*
* phoenix-0002: pipe_through() checks for duplicate pipes.
* Slow: O(new_pipes * existing_pipes) nested list scans.
* Fast: O(new_pipes) with HashSet.contains.
*/
public class PhoenixTest {
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
// warmup
slow.run();
fast.run();
long t0 = System.nanoTime();
slow.run();
long sMs = (System.nanoTime() - t0) / 1_000_000;
long t1 = System.nanoTime();
fast.run();
long fMs = (System.nanoTime() - t1) / 1_000_000;
double r = fMs > 0 ? (double) sMs / fMs : 0;
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n",
label, sMs, sOps, fMs, fOps, r);
}
// -----------------------------------------------------------------------
// phoenix-0001: channel dispatch — event in event_intercepts
// Models: N subscribers each holding a List<String> of K intercepted events.
// Per broadcast: check event membership for all subscribers.
// -----------------------------------------------------------------------
static long dispatchSlow(List<String[]> subscribers, String event, int broadcastCount) {
long matches = 0;
for (int b = 0; b < broadcastCount; b++) {
for (String[] intercepts : subscribers) {
// Elixir: event in event_intercepts (List.member? — O(k))
for (String e : intercepts) {
if (e.equals(event)) {
matches++;
break;
}
}
}
}
return matches;
}
static long dispatchFast(List<Set<String>> subscriberSets, String event, int broadcastCount) {
long matches = 0;
for (int b = 0; b < broadcastCount; b++) {
for (Set<String> intercepts : subscriberSets) {
// MapSet.member? — O(1)
if (intercepts.contains(event)) {
matches++;
}
}
}
return matches;
}
// -----------------------------------------------------------------------
// phoenix-0002: pipe_through duplicate check
// Models: accumulating P pipes one-by-one, checking for duplicates each time.
// -----------------------------------------------------------------------
static int pipeAccumulateSlow(int totalPipes) {
// Elixir: pipes is a List — duplicate check via Enum.find(&1 in pipes)
List<String> pipes = new ArrayList<>();
int duplicatesFound = 0;
for (int i = 0; i < totalPipes; i++) {
String newPipe = "pipeline_" + i;
// O(n) scan for duplicate
boolean isDuplicate = false;
for (String p : pipes) {
if (p.equals(newPipe)) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
// O(n) append via list concat simulation
pipes.add(newPipe);
} else {
duplicatesFound++;
}
}
return duplicatesFound;
}
static int pipeAccumulateFast(int totalPipes) {
// Elixir fix: pipes is a MapSet — duplicate check via MapSet.member?
Set<String> pipes = new HashSet<>();
int duplicatesFound = 0;
for (int i = 0; i < totalPipes; i++) {
String newPipe = "pipeline_" + i;
// O(1) membership check
if (pipes.contains(newPipe)) {
duplicatesFound++;
} else {
pipes.add(newPipe); // O(1) add
}
}
return duplicatesFound;
}
public static void main(String[] args) {
System.out.println("Phoenix CWE-407 Benchmarks");
System.out.println("==========================");
System.out.println();
// --- phoenix-0001: channel dispatch ---
System.out.println("phoenix-0001: channel dispatch event_intercepts (N=10000 subscribers, K=10 intercepts, 200 broadcasts)");
int N_SUBSCRIBERS = 10_000;
int K_INTERCEPTS = 10;
int BROADCASTS = 200;
// Build slow: each subscriber holds a List<String> of K event names
List<String[]> slowSubscribers = new ArrayList<>(N_SUBSCRIBERS);
List<Set<String>> fastSubscribers = new ArrayList<>(N_SUBSCRIBERS);
String[] interceptNames = new String[K_INTERCEPTS];
for (int k = 0; k < K_INTERCEPTS; k++) {
interceptNames[k] = "event_" + k;
}
for (int i = 0; i < N_SUBSCRIBERS; i++) {
slowSubscribers.add(interceptNames.clone());
fastSubscribers.add(new HashSet<>(Arrays.asList(interceptNames)));
}
// Target event is the last one (worst case for list scan)
String targetEvent = "event_" + (K_INTERCEPTS - 1);
long sOps = (long) N_SUBSCRIBERS * BROADCASTS;
long fOps = sOps;
bench(
"dispatch: event in List<intercepts> vs HashSet<intercepts>",
() -> dispatchSlow(slowSubscribers, targetEvent, BROADCASTS),
() -> dispatchFast(fastSubscribers, targetEvent, BROADCASTS),
sOps, fOps
);
// Vary K — show the O(K) scaling
System.out.println();
System.out.println("phoenix-0001: vary K (intercepts per subscriber), N=5000, 100 broadcasts");
int[] kValues = {1, 5, 10, 20, 50};
for (int K : kValues) {
List<String[]> s = new ArrayList<>(5_000);
List<Set<String>> f = new ArrayList<>(5_000);
String[] kNames = new String[K];
for (int k = 0; k < K; k++) kNames[k] = "ev_" + k;
for (int i = 0; i < 5_000; i++) {
s.add(kNames.clone());
f.add(new HashSet<>(Arrays.asList(kNames)));
}
String ev = "ev_" + (K - 1);
long ops = 5_000L * 100;
bench(
String.format("K=%2d intercepts: List.contains vs HashSet.contains", K),
() -> dispatchSlow(s, ev, 100),
() -> dispatchFast(f, ev, 100),
ops, ops
);
}
// --- phoenix-0002: pipe accumulation ---
System.out.println();
System.out.println("phoenix-0002: router scope pipe accumulation (P=2000 pipelines)");
int P = 2_000;
bench(
"pipe_through: ArrayList dup-check vs HashSet dup-check",
() -> pipeAccumulateSlow(P),
() -> pipeAccumulateFast(P),
P, P
);
System.out.println();
System.out.println("Done.");
}
}