game engines/web frameworks: 27 CWE-407 defects + 3 CLEAN; 194 sites, 78 ecosystems
This commit is contained in:
parent
547a9f5738
commit
4d3fcc8e73
76 changed files with 6216 additions and 17 deletions
|
|
@ -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}}
|
||||
|
|
@ -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
|
||||
190
defects/phoenix/unit/PhoenixTest.java
Normal file
190
defects/phoenix/unit/PhoenixTest.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue