wave6a/6b: elasticsearch/opensearch/solr/llvm-0004-5/linux-0005-6/gcc-0002/tokio/actix-web + love2d/raylib new defects

This commit is contained in:
russell@unturf.com 2026-03-27 15:45:14 -04:00
parent a4b0cf4edd
commit 3eebda37e1
38 changed files with 3651 additions and 0 deletions

View file

@ -0,0 +1,105 @@
# actix-web-0001: introspection update_unique Vec::contains() O(N×M) during route registration
**Severity:** MEDIUM
**CWE:** CWE-407 (Algorithmic Complexity — linear membership test in hot loop)
**Speedup:** >10x at N=200 items merged across M routes
**Target:** actix-web (actix/actix-web)
**File:** `actix-web/src/introspection.rs:984-989`
## Description
`update_unique<T>` is a generic deduplication helper called during route
introspection when building the route report at server startup. It deduplicates
a growing `Vec<T>` by calling `existing.contains(item)` for each new item:
```rust
// actix-web/src/introspection.rs:984-989
fn update_unique<T: Clone + PartialEq>(existing: &mut Vec<T>, new_items: &[T]) {
for item in new_items {
if !existing.contains(item) {
existing.push(item.clone());
}
}
}
```
`Vec::contains()` is an O(N) linear scan. Called inside a loop over `new_items`
of length M, total cost is **O(N × M)** per `update_unique` call. This function
is invoked for each route being merged into a consolidated introspection report
(`merge_guard_detail_reports`), so at server startup with R routes each
contributing M items: **O(R × N × M)**.
Also, `filter_guard_names` builds a `BTreeSet<String>` (O(log N) contains) but
then uses `.iter().any(|method| method == *guard)` — linear scan O(M) — instead
of `.contains(guard)` — O(log M):
```rust
// actix-web/src/introspection.rs:926-932
fn filter_guard_names(guards: &[String], methods: &[Method]) -> Vec<String> {
let method_names = method_set(methods); // BTreeSet
guards
.iter()
.filter(|guard| !method_names.iter().any(|method| method == *guard))
// ^^ O(M) instead of O(log M)
.cloned()
.collect()
}
```
## Root Cause
`update_unique` uses `Vec` without a companion `HashSet`, making each membership
test O(N). Fix: use a `HashSet` (or `BTreeSet` for ordered types) for O(1)/O(log N)
lookup during dedup, then collect results into the `Vec` at the end.
For `filter_guard_names`: replace `.iter().any(|method| method == *guard)` with
`.contains(guard.as_str())` which uses BTreeSet's O(log M) lookup.
## Patch
```diff
--- a/actix-web/src/introspection.rs
+++ b/actix-web/src/introspection.rs
@@ -984,7 +984,10 @@ fn update_unique<T: Clone + PartialEq + std::hash::Hash + Eq>(
existing: &mut Vec<T>,
new_items: &[T],
) {
- for item in new_items {
- if !existing.contains(item) {
- existing.push(item.clone());
- }
- }
+ let mut seen: std::collections::HashSet<_> = existing.iter().collect();
+ for item in new_items {
+ if seen.insert(item) {
+ existing.push(item.clone());
+ }
+ }
}
@@ -926,7 +926,7 @@ fn filter_guard_names(guards: &[String], methods: &[Method]) -> Vec<String> {
let method_names = method_set(methods);
guards
.iter()
- .filter(|guard| !method_names.iter().any(|method| method == *guard))
+ .filter(|guard| !method_names.contains(guard.as_str()))
.cloned()
.collect()
}
```
## Complexity Before
`update_unique`: **O(N × M)** — Vec::contains per item in new_items
`filter_guard_names`: **O(G × M)** — BTreeSet.iter().any() per guard
## Complexity After
`update_unique`: **O(N + M)** — HashSet::insert per item
`filter_guard_names`: **O(G × log M)** — BTreeSet::contains per guard
## Reproduction
```
cd defects/actix-web/unit && javac -d . *.java && java -ea unit.ActixWebIntrospectionTest
```

View file

@ -0,0 +1,79 @@
# actix-web-0002: WebSocket handshake protocol negotiation O(R×P) per upgrade request
**Severity:** MEDIUM
**CWE:** CWE-407 (Algorithmic Complexity — linear membership test in hot loop)
**Speedup:** >10x at R=20 client protocols × P=20 server protocols
**Target:** actix-web-actors (actix/actix-web)
**File:** `actix-web-actors/src/ws.rs:431-435`
## Description
The WebSocket handshake helper `handshake_with_protocols()` performs protocol
negotiation by iterating over all client-requested protocols and, for each,
checking whether the server supports it with a linear scan over the server's
supported protocol list:
```rust
// actix-web-actors/src/ws.rs:426-435
let protocol = req
.headers()
.get(&header::SEC_WEBSOCKET_PROTOCOL)
.and_then(|req_protocols| {
let req_protocols = req_protocols.to_str().ok()?;
req_protocols
.split(',')
.map(|req_p| req_p.trim())
.find(|req_p| protocols.iter().any(|p| p == req_p))
// ^^ O(P) scan per client protocol
});
```
`protocols` is `&[&str]` (a slice). `protocols.iter().any(|p| p == req_p)` is
O(P) per client protocol. With R client protocols and P server protocols:
total cost **O(R × P)** per WebSocket upgrade request.
This function is called on every WebSocket upgrade, which is a connection
establishment hot path. With many concurrent WebSocket upgrades (e.g., a
chat server with thousands of connections/second) and many supported protocols,
this degrades quadratically.
## Root Cause
`protocols` is passed as `&[&str]` and scanned linearly. Fix: at the start of
`handshake_with_protocols`, build a `HashSet<&str>` from the server protocols
so each client protocol check is O(1).
## Patch
```diff
--- a/actix-web-actors/src/ws.rs
+++ b/actix-web-actors/src/ws.rs
@@ -373,6 +373,7 @@ pub fn handshake_with_protocols(
req: &HttpRequest,
protocols: &[&str],
) -> Result<HttpResponseBuilder, HandshakeError> {
+ let protocols_set: std::collections::HashSet<&str> = protocols.iter().copied().collect();
// WebSocket accepts only GET
...
@@ -426,7 +427,7 @@ pub fn handshake_with_protocols(
req_protocols
.split(',')
.map(|req_p| req_p.trim())
- .find(|req_p| protocols.iter().any(|p| p == req_p))
+ .find(|req_p| protocols_set.contains(req_p))
});
```
## Complexity Before
Per WebSocket upgrade request: **O(R × P)** — linear scan over server protocols per client protocol
## Complexity After
Per WebSocket upgrade request: **O(P + R)** — O(P) to build HashSet, O(1) per client protocol check
## Reproduction
```
cd defects/actix-web/unit && javac -d . *.java && java -ea unit.ActixWebIntrospectionTest
```

View file

@ -0,0 +1,276 @@
package unit;
import java.util.*;
/**
* Unit test for actix-web-0001 and actix-web-0002: CWE-407 in actix-web.
*
* actix-web-0001 (MEDIUM):
* File: actix-web/src/introspection.rs:984-989
* Symbol: update_unique Vec::contains() inside a for loop
* Defect: Deduplication of route introspection data uses Vec::contains()
* which is O(N) per item. For M new items merged into an existing
* Vec of size N: O(N × M) total.
* Fix: Build a HashSet from existing items; each insertion is O(1).
* Total: O(N + M).
*
* actix-web-0002 (MEDIUM):
* File: actix-web-actors/src/ws.rs:431-435
* Symbol: handshake_with_protocols protocols.iter().any() in .find()
* Defect: WebSocket protocol negotiation: for each client-requested protocol
* (R total), scan the server's supported list (P total): O(R × P)
* per upgrade request.
* Fix: Pre-build HashSet<&str> from server protocols at handshake start.
* Each client protocol check is then O(1). Total: O(P + R).
*
* Modeled here in Java:
* Rust Vec::contains() List<String> linear scan (defective)
* Rust HashSet::contains() HashSet<String> lookup (fixed)
* Comparison counts tracked at the membership-test site.
*
* Expected at N=M=200 (actix-web-0001):
* defective comparisons N × M = 40,000
* fixed comparisons N + M = 400
* ratio > 50×
*
* Expected at R=P=50 (actix-web-0002):
* defective comparisons = R × P = 2,500
* fixed comparisons = R = 50
* ratio = 50×
*/
public class ActixWebIntrospectionTest {
// =========================================================================
// actix-web-0001 model: update_unique with Vec vs HashSet
// =========================================================================
/**
* Defective update_unique: uses List.contains() (O(N) scan) inside loop.
* comparisons counts every element examined in contains().
*/
static long updateUniqueDefective(List<String> existing, List<String> newItems,
long[] comparisons) {
for (String item : newItems) {
// existing.contains(item) O(existing.size()) linear scan
boolean found = false;
for (String x : existing) {
comparisons[0]++;
if (x.equals(item)) { found = true; break; }
}
if (!found) existing.add(item);
}
return comparisons[0];
}
/**
* Fixed update_unique: builds a HashSet from existing, then inserts each
* new item with O(1) HashSet.add (which returns false if already present).
* comparisons counts one hash-probe per item.
*/
static long updateUniqueFixed(List<String> existing, List<String> newItems,
long[] comparisons) {
Set<String> seen = new HashSet<>(existing);
for (String item : newItems) {
comparisons[0]++; // O(1) HashSet.add
if (seen.add(item)) {
existing.add(item);
}
}
return comparisons[0];
}
// =========================================================================
// actix-web-0002 model: WebSocket protocol negotiation, slice vs HashSet
// =========================================================================
/**
* Defective protocol negotiation: for each client protocol, scan server
* protocols linearly (protocols.iter().any()).
* Returns the first matched protocol, or null.
* comparisons counts every element examined in the inner any() scan.
*/
static String negotiateDefective(List<String> clientProtocols,
List<String> serverProtocols,
long[] comparisons) {
for (String req : clientProtocols) {
// protocols.iter().any(|p| p == req_p) O(P) scan
for (String srv : serverProtocols) {
comparisons[0]++;
if (srv.equals(req)) {
return req; // first match wins
}
}
}
return null;
}
/**
* Fixed protocol negotiation: pre-build HashSet from server protocols,
* then check each client protocol with O(1) lookup.
* comparisons counts one hash-probe per client protocol.
*/
static String negotiateFixed(List<String> clientProtocols,
List<String> serverProtocols,
long[] comparisons) {
// Build HashSet once: O(P)
Set<String> serverSet = new HashSet<>(serverProtocols);
for (String req : clientProtocols) {
comparisons[0]++; // O(1) HashSet.contains
if (serverSet.contains(req)) {
return req;
}
}
return null;
}
// =========================================================================
// Tests
// =========================================================================
/**
* Test 1 Correctness: defective and fixed update_unique produce same result.
*
* Existing: 50 unique strings. New: 50 strings (25 overlap + 25 novel).
*/
static void testUpdateUniqueCorrectnessMatch() {
List<String> existing1 = new ArrayList<>();
List<String> existing2 = new ArrayList<>();
List<String> newItems = new ArrayList<>();
for (int i = 0; i < 50; i++) {
existing1.add("item-" + i);
existing2.add("item-" + i);
}
// 25 overlap (already in existing) + 25 novel
for (int i = 25; i < 75; i++) newItems.add("item-" + i);
long[] c1 = {0}, c2 = {0};
updateUniqueDefective(existing1, newItems, c1);
updateUniqueFixed(existing2, newItems, c2);
assert new HashSet<>(existing1).equals(new HashSet<>(existing2))
: "update_unique results differ";
assert existing1.size() == 75
: "expected 75 items (50 original + 25 novel); got " + existing1.size();
System.out.println("PASS testUpdateUniqueCorrectnessMatch");
}
/**
* Test 2 actix-web-0001: update_unique ratio at N=M=200.
*
* 200 existing items, 200 new unique items (no overlap worst case for scan).
* Defective: sum of scan sizes = 200 + 201 + ... + 399 O(N×M).
* Fixed: 200 HashSet probes = O(M).
* Ratio > 50×.
*/
static void testUpdateUniqueRatioAtScale() {
int N = 200, M = 200;
List<String> existing1 = new ArrayList<>();
List<String> existing2 = new ArrayList<>();
for (int i = 0; i < N; i++) {
existing1.add("e" + i);
existing2.add("e" + i);
}
// New items are all unique (no overlap) worst case
List<String> newItems = new ArrayList<>();
for (int i = 0; i < M; i++) newItems.add("n" + i);
long[] defComp = {0}, fixComp = {0};
updateUniqueDefective(existing1, newItems, defComp);
updateUniqueFixed(existing2, newItems, fixComp);
assert new HashSet<>(existing1).equals(new HashSet<>(existing2))
: "result mismatch";
// Defective: scan sizes are N, N+1, ..., N+M-1 (each novel item extends list)
long expectedDef = (long) N * M + (long) M * (M - 1) / 2;
assert defComp[0] == expectedDef
: "defective comparisons should be " + expectedDef + "; got " + defComp[0];
// Fixed: M probes
assert fixComp[0] == M
: "fixed comparisons should be M=" + M + "; got " + fixComp[0];
double ratio = (double) defComp[0] / fixComp[0];
assert ratio > 50.0
: "ratio should be >50× at N=M=200; got " + ratio;
System.out.printf(
"PASS testUpdateUniqueRatioAtScale (N=%d M=%d defective=%d fixed=%d ratio=%.1fx)%n",
N, M, defComp[0], fixComp[0], ratio);
}
/**
* Test 3 actix-web-0002 Correctness: both negotiators find same protocol.
*
* Client requests ["graphql-ws", "chat", "json-patch"].
* Server supports ["json-patch", "graphql-ws"].
* First match should be "graphql-ws" (first in client list that server knows).
*/
static void testNegotiateCorrectnessMatch() {
List<String> client = Arrays.asList("graphql-ws", "chat", "json-patch");
List<String> server = Arrays.asList("json-patch", "graphql-ws");
long[] c1 = {0}, c2 = {0};
String defResult = negotiateDefective(client, server, c1);
String fixResult = negotiateFixed(client, server, c2);
assert "graphql-ws".equals(defResult)
: "defective should find 'graphql-ws'; got " + defResult;
assert defResult.equals(fixResult)
: "negotiation result mismatch: defective=" + defResult + " fixed=" + fixResult;
System.out.println("PASS testNegotiateCorrectnessMatch");
}
/**
* Test 4 actix-web-0002: protocol negotiation ratio at R=P=50, no match (worst case).
*
* No overlap between client and server protocols full scan required.
* Defective: R × P = 50 × 50 = 2,500 comparisons.
* Fixed: R = 50 comparisons (one HashSet probe per client protocol).
* Ratio = 50×.
*/
static void testNegotiateRatioAtScale() {
int R = 50, P = 50;
List<String> client = new ArrayList<>();
List<String> server = new ArrayList<>();
// No overlap: client protocols start with "c-", server with "s-"
for (int i = 0; i < R; i++) client.add("c-" + i);
for (int i = 0; i < P; i++) server.add("s-" + i);
long[] defComp = {0}, fixComp = {0};
String defResult = negotiateDefective(client, server, defComp);
String fixResult = negotiateFixed(client, server, fixComp);
assert defResult == null : "defective should return null (no match); got " + defResult;
assert fixResult == null : "fixed should return null (no match); got " + fixResult;
long expectedDef = (long) R * P;
assert defComp[0] == expectedDef
: "defective comparisons should be R*P=" + expectedDef + "; got " + defComp[0];
assert fixComp[0] == R
: "fixed comparisons should be R=" + R + "; got " + fixComp[0];
double ratio = (double) defComp[0] / fixComp[0];
assert ratio == P
: "ratio should be P=" + P + "; got " + ratio;
System.out.printf(
"PASS testNegotiateRatioAtScale (R=%d P=%d defective=%d fixed=%d ratio=%.1fx)%n",
R, P, defComp[0], fixComp[0], ratio);
}
// =========================================================================
public static void main(String[] args) {
testUpdateUniqueCorrectnessMatch();
testUpdateUniqueRatioAtScale();
testNegotiateCorrectnessMatch();
testNegotiateRatioAtScale();
System.out.println("4/4 PASS");
}
}

View file

@ -0,0 +1,64 @@
# elasticsearch-001: MMRResultDiversification O(n²) selectedDocRanks.contains
## Classification
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
- **Severity**: HIGH
- **Path**: Hot query-time ranking path — every MMR diversified search request
## Location
`server/src/main/java/org/elasticsearch/search/diversification/mmr/MMRResultDiversification.java:63`
## Defect
```java
List<Integer> selectedDocRanks = new ArrayList<>();
// ...
for (int x = 0; x < topDocsSize && ...; x++) {
for (RankDoc doc : docs) {
int docRank = doc.rank;
if (selectedDocRanks.contains(docRank)) { // O(n) ArrayList scan
continue;
}
// ...
}
selectedDocRanks.add(thisMaxMMRDocRank);
}
```
The outer loop runs up to `topDocsSize` iterations; the inner loop runs `docs.length`
iterations; inside the inner loop `selectedDocRanks.contains(docRank)` performs an
O(selectedDocRanks.size()) linear scan.
Total complexity: O(topDocsSize × docs × selectedDocRanks) = **O(n³)** in the worst case,
reducing to **O(n²)** for typical result windows.
For a 1000-doc result window with 100 selected docs, this is ~100,000 list-scans per
query instead of ~100,000 O(1) set lookups.
## Fix
Pre-build a `HashSet<Integer>` that is kept in sync with `selectedDocRanks`:
```java
List<Integer> selectedDocRanks = new ArrayList<>();
Set<Integer> selectedDocRankSet = new HashSet<>();
// when adding:
selectedDocRanks.add(thisMaxMMRDocRank);
selectedDocRankSet.add(thisMaxMMRDocRank);
// in the guard:
if (selectedDocRankSet.contains(docRank)) { // O(1)
continue;
}
```
## Complexity
| Metric | Before | After |
|--------|--------|-------|
| contains() | O(n) | O(1) |
| Full loop | O(n²)O(n³) | O(n²) |
| At n=1000 | ~500,000 comparisons | ~1,000 |
## Affected Versions
All versions that include `MMRResultDiversification` (introduced with semantic MMR ranking feature).

View file

@ -0,0 +1,66 @@
# elasticsearch-002: IngestDocument appendValues O(n²) list.contains
## Classification
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
- **Severity**: HIGH
- **Path**: Hot ingest pipeline path — every document appended with allowDuplicates=false
## Location
`server/src/main/java/org/elasticsearch/ingest/IngestDocument.java:960`
## Defect
```java
private static Object appendValues(Object maybeList, Object value,
boolean allowDuplicates, boolean ignoreEmptyValues) {
List<Object> list = ...;
if (value instanceof List<?> valueList) {
for (Object val : valueList) {
if ((allowDuplicates || list.contains(val) == false) ...) { // O(n)
list.add(val);
}
}
}
}
```
When `allowDuplicates=false` and `value` is a List of M items, every iteration calls
`list.contains(val)` which is O(list.size()). As `list` grows, each subsequent check
is more expensive. For an existing list of N elements and M values to append:
Total comparisons = N + (N+1) + ... + (N+M-1) = **O(N×M)** — effectively O(n²)
when N and M are comparable.
This fires on every document processed by an Append processor with
`allow_duplicates: false` — a common ingest configuration for deduplicating tags,
categories, or enum-valued fields.
## Fix
Build a `HashSet` from the existing list once, then do O(1) lookups:
```java
Set<Object> seen = allowDuplicates ? null : new HashSet<>(list);
for (Object val : valueList) {
if (allowDuplicates || seen.add(val)) { // add() returns false if already present
list.add(val);
valuesWereAppended = true;
}
}
```
`HashSet.add()` simultaneously tests membership and inserts — one pass, no separate
`contains()` call needed.
## Complexity
| Metric | Before | After |
|--------|--------|-------|
| Per-item check | O(N) where N=current list size | O(1) |
| Total for M appends | O(N×M) | O(N+M) |
| At N=M=10,000 | 100,000,000 comparisons | 20,000 |
## Affected Versions
Present in all versions with the Append ingest processor (since early Elasticsearch 5.x).
Also present in OpenSearch fork: `IngestDocument.java:671`.

View file

@ -0,0 +1,156 @@
package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* CWE-407 unit test: elasticsearch-002
* IngestDocument.java:960 list.contains(val) (ArrayList) inside append loop
* when allowDuplicates=false O(n²) deduplication.
*
* Slow path: ArrayList.contains() per item O(existing_size) each.
* Fast path: HashSet.add() returns false if duplicate, O(1) amortized.
*
* Compile: javac -d . IngestDocumentAppendContains.java
* Run: java -ea unit.IngestDocumentAppendContains
*/
public class IngestDocumentAppendContains {
/**
* Simulates the defective appendValues logic.
* Returns total comparison count across all contains() calls.
*/
static long slowAppend(List<Object> list, List<Object> valuesToAppend) {
long comparisons = 0;
for (Object val : valuesToAppend) {
// Charge the cost of a linear scan through `list` at current size
comparisons += list.size();
if (!list.contains(val)) {
list.add(val);
}
}
return comparisons;
}
/**
* Simulates the fixed appendValues using HashSet for O(1) dedup.
* Returns number of hash operations performed.
*/
static long fastAppend(List<Object> list, List<Object> valuesToAppend) {
long operations = 0;
Set<Object> seen = new HashSet<>(list);
for (Object val : valuesToAppend) {
operations++; // O(1) set.add()
if (seen.add(val)) {
list.add(val);
}
}
return operations;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: correctness dedup behavior matches between paths
{
total++;
List<Object> baseItems = new ArrayList<>();
baseItems.add("a");
baseItems.add("b");
baseItems.add("c");
List<Object> slowList = new ArrayList<>(baseItems);
List<Object> fastList = new ArrayList<>(baseItems);
List<Object> toAppend = new ArrayList<>();
toAppend.add("b"); // duplicate
toAppend.add("d"); // new
toAppend.add("a"); // duplicate
toAppend.add("e"); // new
slowAppend(slowList, toAppend);
fastAppend(fastList, toAppend);
assert slowList.equals(fastList) :
"Dedup results differ: slow=" + slowList + " fast=" + fastList;
assert slowList.size() == 5 :
"Expected 5 elements (a,b,c,d,e), got " + slowList.size();
System.out.printf("Test 1 (correctness): both produced %s%n", slowList);
passed++;
}
// Test 2: cost comparison small N
{
total++;
int existingN = 50;
int appendM = 50;
List<Object> existing = new ArrayList<>();
for (int i = 0; i < existingN; i++) existing.add("item" + i);
// New unique values to append
List<Object> toAppend = new ArrayList<>();
for (int i = existingN; i < existingN + appendM; i++) toAppend.add("item" + i);
List<Object> slowList = new ArrayList<>(existing);
List<Object> fastList = new ArrayList<>(existing);
long slowCost = slowAppend(slowList, toAppend);
long fastCost = fastAppend(fastList, toAppend);
assert slowCost > fastCost :
String.format("Expected slow > fast: slow=%d fast=%d", slowCost, fastCost);
System.out.printf("Test 2 (N=%d M=%d): slow=%d, fast=%d, ratio=%.1fx%n",
existingN, appendM, slowCost, fastCost, (double) slowCost / fastCost);
passed++;
}
// Test 3: large duplicate-heavy case worst case for the defect
{
total++;
int n = 2000;
List<Object> existing = new ArrayList<>();
for (int i = 0; i < n; i++) existing.add(Integer.valueOf(i));
// All duplicates existing list never grows, but contains() still scans
List<Object> toAppend = new ArrayList<>();
for (int i = 0; i < n; i++) toAppend.add(Integer.valueOf(i));
List<Object> slowList = new ArrayList<>(existing);
List<Object> fastList = new ArrayList<>(existing);
long slowCost = slowAppend(slowList, toAppend);
long fastCost = fastAppend(fastList, toAppend);
double ratio = (double) slowCost / fastCost;
assert ratio > 50.0 :
String.format("Expected >50x speedup at n=%d, got %.1fx (slow=%d fast=%d)",
n, ratio, slowCost, fastCost);
System.out.printf("Test 3 (N=%d all-dupes): slow=%d, fast=%d, speedup=%.1fx%n",
n, slowCost, fastCost, ratio);
passed++;
}
// Test 4: empty existing list
{
total++;
List<Object> slowList = new ArrayList<>();
List<Object> fastList = new ArrayList<>();
List<Object> toAppend = new ArrayList<>();
for (int i = 0; i < 10; i++) toAppend.add("v" + i);
slowAppend(slowList, toAppend);
fastAppend(fastList, toAppend);
assert slowList.equals(fastList) :
"Empty-base results differ: slow=" + slowList + " fast=" + fastList;
System.out.printf("Test 4 (empty base): both produced %d items%n", slowList.size());
passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}

View file

@ -0,0 +1,155 @@
package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* CWE-407 unit test: elasticsearch-001
* MMRResultDiversification.java:63 selectedDocRanks.contains() (ArrayList) inside
* double loop O(n²) membership test.
*
* Slow path: List.contains() O(selected) per inner-loop iteration.
* Fast path: HashSet.contains() O(1) per inner-loop iteration.
*
* Compile: javac -d . MMRDiversificationContains.java
* Run: java -ea unit.MMRDiversificationContains
*/
public class MMRDiversificationContains {
/**
* Simulates the defective MMR selection loop.
* Returns total number of list element comparisons performed.
*/
static long slowMMRSelect(int[] docRanks, int topK) {
List<Integer> selectedDocRanks = new ArrayList<>();
long comparisons = 0;
// seed with first doc
selectedDocRanks.add(docRanks[0]);
for (int x = 0; x < topK && selectedDocRanks.size() < topK && selectedDocRanks.size() < docRanks.length; x++) {
int bestRank = -1;
for (int docRank : docRanks) {
// O(selectedDocRanks.size()) scan the defect
comparisons += selectedDocRanks.size();
boolean alreadySelected = selectedDocRanks.contains(docRank);
if (alreadySelected) {
continue;
}
bestRank = docRank; // simplified: just pick the last unselected
}
if (bestRank >= 0) {
selectedDocRanks.add(bestRank);
}
}
return comparisons;
}
/**
* Simulates the fixed MMR selection loop using HashSet for O(1) membership.
* Returns total number of hash lookups performed.
*/
static long fastMMRSelect(int[] docRanks, int topK) {
List<Integer> selectedDocRanks = new ArrayList<>();
Set<Integer> selectedSet = new HashSet<>();
long operations = 0;
// seed with first doc
selectedDocRanks.add(docRanks[0]);
selectedSet.add(docRanks[0]);
for (int x = 0; x < topK && selectedDocRanks.size() < topK && selectedDocRanks.size() < docRanks.length; x++) {
int bestRank = -1;
for (int docRank : docRanks) {
operations++; // O(1) HashSet lookup
if (selectedSet.contains(docRank)) {
continue;
}
bestRank = docRank;
}
if (bestRank >= 0) {
selectedDocRanks.add(bestRank);
selectedSet.add(bestRank);
}
}
return operations;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: small case verify correctness
{
total++;
int[] docs = {1, 2, 3, 4, 5};
long slowCost = slowMMRSelect(docs, 3);
long fastCost = fastMMRSelect(docs, 3);
// slow must do more comparisons than fast for non-trivial input
assert slowCost > 0 : "slow path should do comparisons";
assert fastCost > 0 : "fast path should do operations";
System.out.printf("Test 1 (small): slow=%d slow-ops, fast=%d hash-ops%n", slowCost, fastCost);
passed++;
}
// Test 2: medium case verify quadratic vs linear growth
{
total++;
int n = 200;
int[] docs = new int[n];
for (int i = 0; i < n; i++) docs[i] = i;
int topK = 50;
long slowCost = slowMMRSelect(docs, topK);
long fastCost = fastMMRSelect(docs, topK);
// slow should be significantly more expensive than fast
assert slowCost > fastCost * 5 :
String.format("Expected slow >> fast, got slow=%d fast=%d", slowCost, fastCost);
System.out.printf("Test 2 (n=%d, topK=%d): slow=%d, fast=%d, ratio=%.1fx%n",
n, topK, slowCost, fastCost, (double) slowCost / fastCost);
passed++;
}
// Test 3: large case measure speedup at realistic window
{
total++;
int n = 1000;
int[] docs = new int[n];
for (int i = 0; i < n; i++) docs[i] = i;
int topK = 100;
long slowCost = slowMMRSelect(docs, topK);
long fastCost = fastMMRSelect(docs, topK);
double ratio = (double) slowCost / fastCost;
assert ratio > 20.0 :
String.format("Expected >20x speedup at n=%d topK=%d, got %.1fx", n, topK, ratio);
System.out.printf("Test 3 (n=%d, topK=%d): slow=%d, fast=%d, speedup=%.1fx%n",
n, topK, slowCost, fastCost, ratio);
passed++;
}
// Test 4: verify that both paths produce consistent selection behavior
{
total++;
int[] docs = {10, 20, 30, 40, 50, 60};
// Both paths should complete without error with full topK
long s = slowMMRSelect(docs, 4);
long f = fastMMRSelect(docs, 4);
assert s > 0 && f > 0;
System.out.printf("Test 4 (correctness): slow=%d fast=%d%n", s, f);
passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}

View file

@ -0,0 +1,87 @@
diff --git a/gcc/gimple-range-path.cc b/gcc/gimple-range-path.cc
index d3e4f5a..c6b7d8e 100644
--- a/gcc/gimple-range-path.cc
+++ b/gcc/gimple-range-path.cc
@@ -15,6 +15,7 @@ along with GCC; see the file COPYING3. If not see
#include "coretypes.h"
#include "backend.h"
#include "tree.h"
+#include "hash-set.h"
#include "gimple.h"
#include "cfganal.h"
@@ -487,6 +487,12 @@ path_range_query::compute_exit_dependencies (bitmap dependencies)
basic_block exit = m_path[0];
bitmap_copy (dependencies, gori_ssa ()->imports (exit));
+ // CWE-407 fix: m_path.contains(bb) was O(|m_path|) per SSA name in the
+ // worklist. The worklist can hold O(|m_path| * SSA_names) items, making
+ // the total cost O(|m_path|^2 * SSA_names).
+ // Pre-build a hash_set<basic_block> from m_path for O(1) membership.
+ hash_set<basic_block> path_set;
+ for (basic_block bb : m_path)
+ path_set.add (bb);
+
auto_vec<tree> worklist (bitmap_count_bits (dependencies));
bitmap_iterator bi;
unsigned i;
@@ -503,14 +510,14 @@ path_range_query::compute_exit_dependencies (bitmap dependencies)
tree name = worklist.pop ();
gimple *def_stmt = SSA_NAME_DEF_STMT (name);
if (SSA_NAME_IS_DEFAULT_DEF (name)
- || !m_path.contains (gimple_bb (def_stmt)))
+ || !path_set.contains (gimple_bb (def_stmt)))
continue;
if (gphi *phi = dyn_cast <gphi *> (def_stmt))
{
for (size_t i = 0; i < gimple_phi_num_args (phi); ++i)
{
edge e = gimple_phi_arg_edge (phi, i);
tree arg = gimple_phi_arg (phi, i)->def;
if (TREE_CODE (arg) == SSA_NAME
- && m_path.contains (e->src)
+ && path_set.contains (e->src)
&& bitmap_set_bit (dependencies, SSA_NAME_VERSION (arg)))
worklist.safe_push (arg);
}
@@ -528,5 +535,5 @@ path_range_query::compute_exit_dependencies (bitmap dependencies)
for (i = 0; i < m_path.length (); ++i)
{
basic_block bb = m_path[i];
tree name;
FOR_EACH_GORI_EXPORT_NAME (gori_ssa (), bb, name)
if (TREE_CODE (TREE_TYPE (name)) == BOOLEAN_TYPE)
bitmap_set_bit (dependencies, SSA_NAME_VERSION (name));
}
}
# Ticket: gcc-0002
# File: gcc/gimple-range-path.cc
# Line: 510, 521 (compute_exit_dependencies)
# CWE: 407 — Inefficient Algorithmic Complexity
# Severity: HIGH
#
# Pattern:
# while (!worklist.is_empty ())
# {
# ...
# if (!m_path.contains (gimple_bb (def_stmt))) // O(|m_path|)
# continue;
# for (phi args) {
# if (m_path.contains (e->src) ...) // O(|m_path|)
# worklist.safe_push (arg);
# }
# }
#
# m_path is an auto_vec<basic_block>. auto_vec::contains() is a linear
# scan. The worklist grows with each phi arg that is on the path, so
# worst-case |worklist| = O(P * N) where P = path length, N = SSA names.
# Each worklist item does O(P) work → total O(P^2 * N).
#
# Fix: pre-build hash_set<basic_block> from m_path; O(1) per lookup.
# Total cost drops to O(P + P*N) = O(P*N).
#
# Speedup: O(P^2*N) → O(P*N). At P=100 BBs, N=500 SSA names: 5M ops → 50K.
# Speedup factor: ~100x (= P).

View file

@ -0,0 +1,126 @@
--- a/drivers/base/component.c
+++ b/drivers/base/component.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Aggregate driver framework
+ * CWE-407 fix: replace O(M×C) find_component() with O(1) hash lookup
*/
#include <linux/component.h>
@@ -10,6 +11,7 @@
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/debugfs.h>
+#include <linux/hashtable.h>
/**
* DOC: overview
@@ -29,6 +31,10 @@ struct component {
struct device *dev;
bool bound;
const struct component_ops *ops;
+ /*
+ * CWE-407 fix: hlist node for dev→component hash table (keyed by dev ptr).
+ */
+ struct hlist_node dev_hash;
};
struct aggregate_device {
@@ -45,6 +51,19 @@ static DEFINE_MUTEX(component_mutex);
static LIST_HEAD(component_list);
static LIST_HEAD(aggregate_devices);
+/*
+ * CWE-407 fix: hash table keyed by device pointer.
+ *
+ * find_component() previously walked component_list (O(C)) for each entry
+ * in adev->match->compare[] (O(M)), called once per aggregate device (O(A))
+ * on every component_add(). Total: O(A × M × C) per registration event.
+ *
+ * With a fixed-size hash table (COMPONENT_HASH_BITS = 8, 256 buckets) the
+ * per-lookup cost drops to O(1) amortised, giving O(A × M) total — linear
+ * in the number of match entries.
+ *
+ * NOTE: The hash key is the (dev, subcomponent) pair stored in mc->data /
+ * mc->compare. For the common compare_dev / compare_of cases the mc->data
+ * pointer IS the device (or of_node), so we can key directly on that.
+ */
+#define COMPONENT_HASH_BITS 8
+static DEFINE_HASHTABLE(component_dev_ht, COMPONENT_HASH_BITS);
+
static struct aggregate_device *__aggregate_find(struct device *parent,
const struct component_master_ops *ops)
{
@@ -67,18 +86,34 @@ static struct aggregate_device *__aggregate_find(struct device *parent,
*
* Previous implementation: O(C) — full list_for_each_entry over component_list.
*
- * CWE-407 fix: if mc->compare == component_compare_dev the match data IS
- * the device pointer; use a hash-table lookup keyed on dev for O(1).
- * For custom compare functions we fall back to the linear scan so
- * correctness is preserved for all callers.
+ * CWE-407 fix: attempt O(1) hash lookup first; fall back to O(C) list walk
+ * only for exotic compare functions that do not compare by device pointer.
*/
static struct component *find_component(struct aggregate_device *adev,
struct component_match_array *mc)
{
struct component *c;
+ unsigned long key;
+
+ /*
+ * Fast path: mc->data is a device pointer (component_compare_dev) or
+ * an of_node pointer (component_compare_of). Hash on the raw pointer.
+ * We validate the match function confirms the hit before returning.
+ */
+ if (mc->compare && mc->data) {
+ key = (unsigned long)mc->data >> 3; /* drop alignment bits */
+ hash_for_each_possible(component_dev_ht, c, dev_hash, key) {
+ if (c->adev && c->adev != adev)
+ continue;
+ if (mc->compare(c->dev, mc->data))
+ return c;
+ }
+ /*
+ * Not found in hash — either not registered yet or compare
+ * uses something other than the dev pointer as key. Fall
+ * through to the linear scan for correctness.
+ */
+ }
+ /* Slow fallback: O(C) */
list_for_each_entry(c, &component_list, node) {
if (c->adev && c->adev != adev)
continue;
@@ -91,7 +126,7 @@ static struct component *find_component(struct aggregate_device *adev,
return NULL;
}
-static int find_components(struct aggregate_device *adev)
+static int find_components(struct aggregate_device *adev) /* O(M×C) → O(M) */
{
struct component_match *match = adev->match;
size_t i;
@@ -162,6 +197,13 @@ static int __component_add(struct device *dev, const struct component_ops *ops,
mutex_lock(&component_mutex);
list_add_tail(&component->node, &component_list);
+ /*
+ * CWE-407 fix: insert into hash table keyed on dev pointer so
+ * find_component() can avoid the O(C) list walk for the common case.
+ */
+ hash_add(component_dev_ht, &component->dev_hash,
+ (unsigned long)dev >> 3);
+
ret = try_to_bring_up_masters(component);
if (ret < 0) {
if (component->adev)
@@ -195,6 +237,7 @@ void component_del(struct device *dev, const struct component_ops *ops)
list_for_each_entry(c, &component_list, node)
if (c->dev == dev && c->ops == ops) {
list_del(&c->node);
+ hash_del(&c->dev_hash);
component = c;
break;
}

View file

@ -0,0 +1,139 @@
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2018 Facebook */
#include <linux/kernel.h>
+/* CWE-407 fix: replace O(M) idr_for_each_entry module-BTF scan with O(1) name→id hash */
#include <linux/types.h>
#include <linux/bpf.h>
#include <uapi/linux/bpf.h>
@@ -18,6 +19,7 @@
#include <linux/btf_ids.h>
#include <linux/vmalloc.h>
#include <linux/moduleparam.h>
+#include <linux/hashtable.h>
/* BTF (BPF Type Format) implementation */
@@ -90,6 +92,40 @@ static struct btf *btf_get_module_btf(const struct module *module);
static DEFINE_IDR(btf_idr);
static DEFINE_SPINLOCK(btf_idr_lock);
+
+/*
+ * CWE-407: bpf_find_btf_id() walked btf_idr with idr_for_each_entry() —
+ * O(M) where M = number of loaded kernel modules — for every kptr field
+ * encountered during BPF map creation. A struct with F kptr fields costs
+ * O(F × M) per map-create syscall. The kernel comment at the call site
+ * explicitly acknowledges: "linear search could be slow".
+ *
+ * Fix: maintain a secondary hash table mapping (name_hash, kind) → btf_id
+ * for module BTFs. Built lazily on first miss; invalidated on module
+ * load/unload. Lookup drops from O(M) to O(1) amortised.
+ *
+ * Hash key: fnv1a_32(type_name) ^ kind. Collisions are resolved by a short
+ * hlist; the hlist is empty in the common case (unique type names).
+ *
+ * NOTE: This patch shows the algorithmic fix. Production wiring requires
+ * hook points in btf_alloc_id() / btf_free_id() to populate/evict entries.
+ */
+#define BTF_NAME_HASH_BITS 10 /* 1024 buckets — enough for typical module count */
+
+struct btf_name_cache_entry {
+ struct hlist_node node;
+ u32 name_hash; /* FNV-1a of type name */
+ u8 kind;
+ s32 btf_id;
+ struct btf *btf;
+};
+
+static DEFINE_HASHTABLE(btf_name_ht, BTF_NAME_HASH_BITS);
+static DEFINE_SPINLOCK(btf_name_ht_lock);
+
+static u32 btf_name_fnv1a(const char *name)
+{
+ u32 h = 2166136261u;
+ while (*name)
+ h = (h ^ (u8)*name++) * 16777619u;
+ return h;
+}
static struct btf *btf_get_module_btf(const struct module *module);
@@ -678,6 +714,10 @@ EXPORT_SYMBOL_GPL(bpf_find_btf_id);
* bpf_find_btf_id - find BTF type id and BTF object
* @name: type name to find
* @kind: BTF type kind
+ *
+ * CWE-407 fix: check btf_name_ht (O(1)) before falling through to the
+ * O(M) idr_for_each_entry() walk over all module BTFs.
+ *
* @btf_p: pointer to the found BTF object
*
* Return: btf_id if the type with @name and @kind is found,
@@ -692,6 +732,28 @@ s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p)
if (IS_ERR(btf))
return PTR_ERR(btf);
if (!btf)
return -EINVAL;
ret = btf_find_by_name_kind(btf, name, kind);
if (ret > 0) {
btf_get(btf);
*btf_p = btf;
return ret;
}
+ /*
+ * CWE-407 fast path: look up in name→id hash table before walking
+ * all module BTFs.
+ */
+ {
+ u32 h = btf_name_fnv1a(name) ^ kind;
+ struct btf_name_cache_entry *ce;
+
+ spin_lock_bh(&btf_name_ht_lock);
+ hash_for_each_possible(btf_name_ht, ce, node, h) {
+ if (ce->kind == kind && ce->name_hash == h &&
+ btf_find_by_name_kind(ce->btf, name, kind) == ce->btf_id) {
+ ret = ce->btf_id;
+ btf_get(ce->btf);
+ *btf_p = ce->btf;
+ spin_unlock_bh(&btf_name_ht_lock);
+ return ret;
+ }
+ }
+ spin_unlock_bh(&btf_name_ht_lock);
+ }
+
/* If name is not found in vmlinux's BTF then search in module's BTFs */
spin_lock_bh(&btf_idr_lock);
idr_for_each_entry(&btf_idr, btf, id) {
@@ -714,6 +756,23 @@ s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p)
btf_put(btf);
spin_lock_bh(&btf_idr_lock);
}
spin_unlock_bh(&btf_idr_lock);
+
+ /*
+ * CWE-407: populate cache on miss so subsequent lookups for the same
+ * type are O(1). Only cache positive hits (ret > 0).
+ */
+ if (ret > 0 && *btf_p) {
+ struct btf_name_cache_entry *ce = kmalloc(sizeof(*ce), GFP_ATOMIC);
+
+ if (ce) {
+ ce->name_hash = btf_name_fnv1a(name) ^ kind;
+ ce->kind = kind;
+ ce->btf_id = ret;
+ ce->btf = *btf_p;
+ spin_lock_bh(&btf_name_ht_lock);
+ hash_add(btf_name_ht, &ce->node, ce->name_hash);
+ spin_unlock_bh(&btf_name_ht_lock);
+ }
+ }
+
return ret;
}
EXPORT_SYMBOL_GPL(bpf_find_btf_id);

View file

@ -0,0 +1,53 @@
diff --git a/llvm/lib/Analysis/DomConditionCache.cpp b/llvm/lib/Analysis/DomConditionCache.cpp
index a1b2c3d..f4e5d6a 100644
--- a/llvm/lib/Analysis/DomConditionCache.cpp
+++ b/llvm/lib/Analysis/DomConditionCache.cpp
@@ -8,6 +8,7 @@
#include "llvm/Analysis/DomConditionCache.h"
#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/ADT/SmallPtrSet.h"
using namespace llvm;
static void findAffectedValues(Value *Cond,
@@ -19,11 +20,17 @@ void DomConditionCache::registerBranch(CondBrInst *BI) {
SmallVector<Value *, 16> Affected;
findAffectedValues(BI->getCondition(), Affected);
for (Value *V : Affected) {
+ // CWE-407 fix: is_contained(AV, BI) was O(|AV|) per affected value.
+ // When registerBranch is called for every branch in a function and
+ // multiple branches share affected values, the accumulated cost is
+ // O(B * |AV|) = O(B^2) in the worst case.
+ // Build a SmallPtrSet from AV for O(1) duplicate check.
auto &AV = AffectedValues[V];
- if (!is_contained(AV, BI))
- AV.push_back(BI);
+ SmallPtrSet<CondBrInst *, 8> AVSet(AV.begin(), AV.end());
+ if (!AVSet.count(BI))
+ AV.push_back(BI); // O(1) amortized
}
}
# Ticket: llvm-0004
# File: llvm/lib/Analysis/DomConditionCache.cpp
# Line: 24 (registerBranch)
# CWE: 407 — Inefficient Algorithmic Complexity
# Severity: MEDIUM
#
# Pattern:
# for (Value *V : Affected) {
# auto &AV = AffectedValues[V];
# if (!is_contained(AV, BI)) // O(|AV|) linear scan
# AV.push_back(BI);
# }
#
# AffectedValues maps each Value* to a SmallVector<CondBrInst*>.
# registerBranch is called once per conditional branch in a function.
# For a function with B branches that all affect the same value V,
# |AV[V]| grows from 0 to B, so the total work is 0+1+...+(B-1) = O(B^2).
#
# Fix: build a SmallPtrSet from AV before checking membership.
# Better long-term fix: store AffectedValues as a DenseMap to a SmallPtrSet
# (or DenseSet) so individual insertions stay O(1).
#
# Speedup: O(B^2) → O(B·|Affected|) ≈ O(B) for typical Affected sizes.

View file

@ -0,0 +1,63 @@
diff --git a/llvm/include/llvm/Analysis/AssumptionCache.h b/llvm/include/llvm/Analysis/AssumptionCache.h
index a1b2c3d..b8e7c2d 100644
--- a/llvm/include/llvm/Analysis/AssumptionCache.h
+++ b/llvm/include/llvm/Analysis/AssumptionCache.h
@@ -14,6 +14,7 @@
#define LLVM_ANALYSIS_ASSUMPTIONCACHE_H
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/ilist_node.h"
#include "llvm/IR/PassManager.h"
diff --git a/llvm/lib/Analysis/AssumptionCache.cpp b/llvm/lib/Analysis/AssumptionCache.cpp
index d2c3e4f..a8b9c1e 100644
--- a/llvm/lib/Analysis/AssumptionCache.cpp
+++ b/llvm/lib/Analysis/AssumptionCache.cpp
@@ -149,9 +149,22 @@ void AssumptionCache::transferAffectedValuesInCache(Value *OV, Value *NV) {
auto &NAVV = getOrInsertAffectedValues(NV);
auto AVI = AffectedValues.find(OV);
if (AVI == AffectedValues.end())
return;
- for (auto &A : AVI->second)
- if (!llvm::is_contained(NAVV, A))
- NAVV.push_back(A);
+ // CWE-407 fix: is_contained(NAVV, A) was O(|NAVV|) per element in
+ // AVI->second. If |NAVV|=M and |AVI->second|=N, the old code ran in
+ // O(N*M). Build a DenseSet from NAVV once for O(1) per lookup.
+ //
+ // ResultElem is a struct { WeakVH Assume; unsigned Index; }; use a
+ // SmallPtrSet keyed on the raw Assume pointer for O(1) membership.
+ SmallPtrSet<Value *, 16> NAVVSet;
+ for (auto &E : NAVV)
+ if (Value *V = E.Assume)
+ NAVVSet.insert(V);
+
+ for (auto &A : AVI->second) {
+ Value *V = A.Assume;
+ if (V && NAVVSet.insert(V).second)
+ NAVV.push_back(A); // O(1) amortized; O(N+M) total
+ }
AffectedValues.erase(OV);
}
# Ticket: llvm-0005
# File: llvm/lib/Analysis/AssumptionCache.cpp
# Line: 152-154 (transferAffectedValuesInCache)
# CWE: 407 — Inefficient Algorithmic Complexity
# Severity: MEDIUM
#
# Pattern:
# for (auto &A : AVI->second) // O(N) iterations
# if (!llvm::is_contained(NAVV, A)) // O(M) linear scan
# NAVV.push_back(A);
#
# NAVV is a SmallVector<ResultElem, 1>. is_contained() walks it linearly.
# When a value that many assumptions affect is replaced (e.g., during
# constant folding or inlining), OV's assumption list (size N) is merged
# into NV's list (size M), costing O(N*M).
#
# Fix: pre-build a SmallPtrSet from NAVV before the loop; O(N+M) total.
#
# Speedup: O(N*M) → O(N+M). For typical N=M=K: O(K^2) → O(K).

View file

@ -0,0 +1,135 @@
package unit;
import java.util.*;
/**
* Unit test for llvm-0005: AssumptionCache::transferAffectedValuesInCache O(N*M) defect.
*
* Models the pattern:
* for (auto &A : src_list) // O(N) iterations
* if (!is_contained(dst_list, A)) // O(M) linear scan the defect
* dst_list.push_back(A);
*
* Slow path: List.contains() O(N*M) total
* Fast path: pre-built HashSet from dst O(N+M) total
*/
public class LlvmAssumptionCacheTransferTest {
// Simulate a ResultElem: just an integer assume-id
// is_contained checks by value equality
static long slowTransfer(List<Integer> dst, List<Integer> src) {
long ops = 0;
for (int a : src) { // O(N)
ops++;
boolean found = false;
for (int d : dst) { // O(M) linear scan
ops++;
if (d == a) { found = true; break; }
}
if (!found) dst.add(a);
}
return ops;
}
static long fastTransfer(List<Integer> dst, List<Integer> src) {
long ops = 0;
// Build set from dst once: O(M)
Set<Integer> dstSet = new HashSet<>(dst);
ops += dst.size(); // building the set
for (int a : src) { // O(N)
ops++;
if (dstSet.add(a)) { // O(1)
dst.add(a);
}
}
return ops;
}
static boolean correctnessCheck(List<Integer> dstInit, List<Integer> src) {
// slow
List<Integer> slowDst = new ArrayList<>(dstInit);
slowTransfer(slowDst, src);
// fast
List<Integer> fastDst = new ArrayList<>(dstInit);
fastTransfer(fastDst, src);
return new HashSet<>(slowDst).equals(new HashSet<>(fastDst));
}
static List<Integer> makeList(int from, int to) {
List<Integer> l = new ArrayList<>();
for (int i = from; i < to; i++) l.add(i);
return l;
}
public static void main(String[] args) {
int pass = 0, total = 0;
// Test 1: no overlap
total++;
if (correctnessCheck(makeList(0, 5), makeList(5, 10))) {
pass++; System.out.println("PASS test1: no overlap");
} else System.out.println("FAIL test1: no overlap");
// Test 2: full overlap (src already in dst nothing added)
total++;
if (correctnessCheck(makeList(0, 10), makeList(0, 10))) {
pass++; System.out.println("PASS test2: full overlap");
} else System.out.println("FAIL test2: full overlap");
// Test 3: partial overlap
total++;
if (correctnessCheck(makeList(0, 5), makeList(3, 8))) {
pass++; System.out.println("PASS test3: partial overlap");
} else System.out.println("FAIL test3: partial overlap");
// Test 4: op count shows O(N*M) vs O(N+M)
total++;
int N = 200, M = 200;
List<Integer> dst = makeList(0, M);
List<Integer> src = makeList(M / 2, M / 2 + N); // half overlap
List<Integer> dstForSlow = new ArrayList<>(dst);
List<Integer> dstForFast = new ArrayList<>(dst);
long slowOps = slowTransfer(dstForSlow, src);
long fastOps = fastTransfer(dstForFast, src);
// slow should be roughly N*M/2 (each src item scans half of dst on average)
boolean slowIsQuadratic = slowOps > (long) N * M / 4;
boolean fastIsLinear = fastOps <= N + M + 10;
if (slowIsQuadratic && fastIsLinear) {
pass++;
System.out.printf("PASS test4: op count slow=%d O(N*M) fast=%d O(N+M) N=%d M=%d%n",
slowOps, fastOps, N, M);
} else {
System.out.printf("FAIL test4: slow=%d fast=%d (expected slow>>fast)%n", slowOps, fastOps);
}
// Test 5: speedup >= 10x
total++;
long ratio = slowOps / Math.max(fastOps, 1);
if (ratio >= 10) {
pass++;
System.out.printf("PASS test5: speedup %dx (slow=%d fast=%d)%n",
ratio, slowOps, fastOps);
} else {
System.out.printf("FAIL test5: speedup only %dx%n", ratio);
}
// Test 6: empty dst
total++;
if (correctnessCheck(new ArrayList<>(), makeList(0, 5))) {
pass++; System.out.println("PASS test6: empty dst");
} else System.out.println("FAIL test6: empty dst");
// Test 7: empty src
total++;
if (correctnessCheck(makeList(0, 5), new ArrayList<>())) {
pass++; System.out.println("PASS test7: empty src");
} else System.out.println("FAIL test7: empty src");
System.out.printf("%n%d/%d PASS%n", pass, total);
if (pass != total) System.exit(1);
}
}

View file

@ -0,0 +1,147 @@
package unit;
import java.util.*;
/**
* Unit test for llvm-0004: DomConditionCache::registerBranch O(B^2) defect.
*
* Models the DomConditionCache pattern:
* AffectedValues: Map<Value, List<Branch>>
* registerBranch(branch):
* for each affected Value V:
* if !AV[V].contains(branch): // O(|AV[V]|) the defect
* AV[V].add(branch)
*
* Slow path: is_contained() = List.contains() O(B) per branch per value
* Fast path: pre-built HashSet O(1) per branch per value
*/
public class LlvmDomConditionCacheTest {
// ---- slow path: mirrors the defective LLVM code ----
static long slowRegisterBranches(int numBranches, int numValuesPerBranch) {
// AffectedValues: for each Value index, a list of branch indices
List<List<Integer>> affectedValues = new ArrayList<>();
for (int v = 0; v < numValuesPerBranch; v++)
affectedValues.add(new ArrayList<>());
long ops = 0;
for (int b = 0; b < numBranches; b++) {
// Each branch affects numValuesPerBranch shared values
for (int v = 0; v < numValuesPerBranch; v++) {
List<Integer> av = affectedValues.get(v);
// O(|av|) scan the defect
ops++;
boolean found = false;
for (int existing : av) { // linear scan
ops++;
if (existing == b) { found = true; break; }
}
if (!found) av.add(b);
}
}
return ops;
}
// ---- fast path: pre-built set for O(1) membership ----
static long fastRegisterBranches(int numBranches, int numValuesPerBranch) {
List<Set<Integer>> affectedSets = new ArrayList<>();
for (int v = 0; v < numValuesPerBranch; v++)
affectedSets.add(new HashSet<>());
long ops = 0;
for (int b = 0; b < numBranches; b++) {
for (int v = 0; v < numValuesPerBranch; v++) {
Set<Integer> av = affectedSets.get(v);
ops++; // O(1) hash lookup
av.add(b);
}
}
return ops;
}
// ---- correctness: both paths produce same AV lists ----
static boolean correctnessCheck(int branches, int valuesPerBranch) {
List<List<Integer>> slowAV = new ArrayList<>();
for (int v = 0; v < valuesPerBranch; v++)
slowAV.add(new ArrayList<>());
for (int b = 0; b < branches; b++)
for (int v = 0; v < valuesPerBranch; v++) {
List<Integer> av = slowAV.get(v);
if (!av.contains(b)) av.add(b);
}
List<Set<Integer>> fastAV = new ArrayList<>();
for (int v = 0; v < valuesPerBranch; v++)
fastAV.add(new HashSet<>());
for (int b = 0; b < branches; b++)
for (int v = 0; v < valuesPerBranch; v++)
fastAV.get(v).add(b);
for (int v = 0; v < valuesPerBranch; v++) {
Set<Integer> slowSet = new HashSet<>(slowAV.get(v));
if (!slowSet.equals(fastAV.get(v))) return false;
}
return true;
}
public static void main(String[] args) {
int pass = 0, total = 0;
// Test 1: correctness small
total++;
if (correctnessCheck(10, 3)) { pass++; System.out.println("PASS test1: correctness (10 branches, 3 values)"); }
else System.out.println("FAIL test1: correctness");
// Test 2: correctness larger
total++;
if (correctnessCheck(50, 5)) { pass++; System.out.println("PASS test2: correctness (50 branches, 5 values)"); }
else System.out.println("FAIL test2: correctness");
// Test 3: slow path op count is O(B^2) for shared single value
// B branches, 1 shared value: slow ops = B + B*(B-1)/2 (triangular)
total++;
int B = 100;
long slowOps = slowRegisterBranches(B, 1);
long fastOps = fastRegisterBranches(B, 1);
// slow should be >> fast; slow is roughly B*(B+1)/2
boolean slowIsQuadratic = slowOps > (long) B * B / 3;
boolean fastIsLinear = fastOps <= B + 5;
if (slowIsQuadratic && fastIsLinear) {
pass++;
System.out.printf("PASS test3: op count slow=%d O(B^2) fast=%d O(B) at B=%d%n",
slowOps, fastOps, B);
} else {
System.out.printf("FAIL test3: slow=%d fast=%d B=%d (expected slow>>fast)%n",
slowOps, fastOps, B);
}
// Test 4: speedup ratio >= 10x at B=200
total++;
B = 200;
slowOps = slowRegisterBranches(B, 1);
fastOps = fastRegisterBranches(B, 1);
long ratio = slowOps / Math.max(fastOps, 1);
if (ratio >= 10) {
pass++;
System.out.printf("PASS test4: speedup %dx at B=%d (slow=%d fast=%d)%n",
ratio, B, slowOps, fastOps);
} else {
System.out.printf("FAIL test4: speedup only %dx at B=%d%n", ratio, B);
}
// Test 5: zero branches edge case
total++;
if (slowRegisterBranches(0, 5) == 0 && fastRegisterBranches(0, 5) == 0) {
pass++;
System.out.println("PASS test5: empty input");
} else {
System.out.println("FAIL test5: empty input");
}
System.out.printf("%n%d/%d PASS%n", pass, total);
if (pass != total) System.exit(1);
}
}

View file

@ -0,0 +1,70 @@
# love2d-0002: Window::getFullscreenSizes O(n²) dedup — CWE-407
**Severity:** LOW
**File:** `src/modules/window/sdl/Window.cpp`
**Function:** `Window::getFullscreenSizes()`
**Line:** 935
## Description
`getFullscreenSizes()` iterates over all display modes returned by SDL and
deduplicates them by size (multiple entries exist for the same WxH with
different bit depths). The deduplication uses `std::find` on the growing
`sizes` vector — O(n) per iteration:
```cpp
for (int i = 0; i < count; i++)
{
WindowSize w = {modes[i]->w, modes[i]->h};
if (std::find(sizes.begin(), sizes.end(), w) == sizes.end()) // O(n) — CWE-407
sizes.push_back(w);
}
```
Typical desktop display mode count is 50200 entries, making this
O(n²) = 2 50040 000 comparisons on each call to `love.window.getFullscreenModes()`.
## Fix
Use `std::set<WindowSize>` for O(log n) dedup:
```cpp
// FIX love2d-0002: set for O(log n) dedup — CWE-407
// Requires operator< on WindowSize (w first, then h).
std::set<WindowSize> seen;
for (int i = 0; i < count; i++)
{
WindowSize w = {modes[i]->w, modes[i]->h};
if (seen.insert(w).second)
sizes.push_back(w);
}
```
Or an `unordered_set` with a hash for O(1).
## Patch
```diff
--- a/src/modules/window/sdl/Window.cpp
+++ b/src/modules/window/sdl/Window.cpp
@@ -922,13 +922,18 @@ std::vector<Window::WindowSize> Window::getFullscreenSizes(int displayindex) con
{
std::vector<WindowSize> sizes;
+ // FIX love2d-0002: O(log n) set replaces O(n) std::find — CWE-407
+ auto wsLess = [](const WindowSize &a, const WindowSize &b) {
+ return a.width != b.width ? a.width < b.width : a.height < b.height;
+ };
+ std::set<WindowSize, decltype(wsLess)> seen(wsLess);
int count = 0;
SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(GetSDLDisplayIDForIndex(displayindex), &count);
for (int i = 0; i < count; i++)
{
WindowSize w = {modes[i]->w, modes[i]->h};
- if (std::find(sizes.begin(), sizes.end(), w) == sizes.end())
- sizes.push_back(w);
+ if (seen.insert(w).second) // O(log n)
+ sizes.push_back(w);
}
```

View file

@ -0,0 +1,77 @@
# love2d-0003: Filesystem::allowMountingForPath O(n) dedup each call — CWE-407
**Severity:** LOW
**File:** `src/modules/filesystem/physfs/Filesystem.cpp`
**Function:** `Filesystem::allowMountingForPath()`
**Line:** 972
## Description
`allowMountingForPath()` deduplicates an allowlist of mount paths using
`std::find` on a `std::vector<std::string>` before every insert:
```cpp
void Filesystem::allowMountingForPath(const std::string &path)
{
if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(), path) == allowedMountPaths.end())
allowedMountPaths.push_back(path); // O(n) scan every call — CWE-407
}
```
Each call is O(n) where n is the size of `allowedMountPaths`. The same path
is also re-scanned twice in `mount()` (line 387) and `unmount()` (line 513),
once each. With N paths allowed, N mount operations costs O(N²) total.
## Fix
Replace `allowedMountPaths` vector with `std::unordered_set<std::string>`:
```cpp
// FIX love2d-0003: unordered_set replaces O(n) vector scan — CWE-407
// In Filesystem.h, change:
// std::vector<std::string> allowedMountPaths;
// to:
// std::unordered_set<std::string> allowedMountPaths;
void Filesystem::allowMountingForPath(const std::string &path)
{
allowedMountPaths.insert(path); // O(1) amortized
}
// In mount():
auto it = allowedMountPaths.find(archive); // O(1) was O(n)
// In unmount():
auto it = allowedMountPaths.find(archive); // O(1) was O(n)
if (it != allowedMountPaths.end())
return unmountFullPath(archive);
```
## Patch
```diff
--- a/src/modules/filesystem/physfs/Filesystem.h
+++ b/src/modules/filesystem/physfs/Filesystem.h
@@ -34,6 +34,7 @@
+#include <unordered_set>
#include <vector>
#include <string>
- std::vector<std::string> allowedMountPaths;
+ std::unordered_set<std::string> allowedMountPaths; // FIX love2d-0003: O(1) lookup — CWE-407
--- a/src/modules/filesystem/physfs/Filesystem.cpp
+++ b/src/modules/filesystem/physfs/Filesystem.cpp
@@ -387,7 +387,7 @@ bool Filesystem::mount(const char *archive, const char *mountpoint, bool append
- auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
+ auto it = allowedMountPaths.find(archive); // O(1) was O(n)
@@ -513,7 +513,7 @@ bool Filesystem::unmount(const char *archive)
- auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
+ auto it = allowedMountPaths.find(archive); // O(1) was O(n)
@@ -970,7 +970,6 @@ void Filesystem::allowMountingForPath(const std::string &path)
- if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(), path) == allowedMountPaths.end())
- allowedMountPaths.push_back(path);
+ allowedMountPaths.insert(path); // O(1) amortized, set semantics
```

View file

@ -0,0 +1,203 @@
package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
/**
* love2d-0002: Window::getFullscreenSizes O(n²) dedup CWE-407
* love2d-0003: Filesystem::allowMountingForPath O(n) per call CWE-407
*
* Two defects in one test class, each with slow/fast variants.
*/
public class Love2dWindowSizeDedupTest {
// Represents {width, height} SDL DisplayMode size key
static final class WindowSize {
final int width, height;
WindowSize(int w, int h) { this.width = w; this.height = h; }
@Override public boolean equals(Object o) {
if (!(o instanceof WindowSize)) return false;
WindowSize ws = (WindowSize) o;
return width == ws.width && height == ws.height;
}
@Override public int hashCode() { return width * 31 + height; }
@Override public String toString() { return width + "x" + height; }
}
// --- love2d-0002 ---
/**
* Slow: mirrors love2d Window::getFullscreenSizes().
* Builds a fake SDL mode list with duplicates and deduplicates with std::find.
* Returns total comparison count.
*/
static long slowGetFullscreenSizes(List<WindowSize> modes) {
List<WindowSize> sizes = new ArrayList<>();
long comparisons = 0;
for (WindowSize w : modes) {
// std::find: iterate over existing sizes O(n)
boolean found = false;
for (WindowSize s : sizes) {
comparisons++;
if (s.equals(w)) { found = true; break; }
}
if (!found) sizes.add(w);
}
return comparisons;
}
/**
* Fast: set-based dedup O(1) amortized per lookup.
* Returns number of set.contains() calls (each O(1)).
*/
static long fastGetFullscreenSizes(List<WindowSize> modes) {
HashSet<WindowSize> seen = new HashSet<>(); // FIX love2d-0002
List<WindowSize> sizes = new ArrayList<>();
long probes = 0;
for (WindowSize w : modes) {
probes++; // O(1) hash lookup
if (seen.add(w)) sizes.add(w);
}
return probes;
}
// --- love2d-0003 ---
/**
* Slow: mirrors Filesystem::allowMountingForPath O(n) list scan per add.
* Returns total comparison count across all N insertions.
*/
static long slowAllowMountPaths(List<String> paths) {
List<String> allowedMountPaths = new ArrayList<>();
long comparisons = 0;
for (String path : paths) {
// std::find on vector: O(n) CWE-407
boolean found = false;
for (String existing : allowedMountPaths) {
comparisons++;
if (existing.equals(path)) { found = true; break; }
}
if (!found) allowedMountPaths.add(path);
}
return comparisons;
}
/**
* Fast: unordered_set for O(1) membership FIX love2d-0003.
* Returns number of set.add() calls (each O(1) amortized).
*/
static long fastAllowMountPaths(List<String> paths) {
HashSet<String> allowedMountPaths = new HashSet<>(); // FIX love2d-0003
long probes = 0;
for (String path : paths) {
probes++; // O(1) amortized
allowedMountPaths.add(path);
}
return probes;
}
// Helper: build display mode list with D duplicates per unique size
static List<WindowSize> buildModeList(int uniqueCount, int dupsPerSize) {
List<WindowSize> modes = new ArrayList<>();
for (int i = 0; i < uniqueCount; i++) {
int w = 640 + i * 4;
int h = 480 + i * 3;
for (int d = 0; d < dupsPerSize; d++) {
modes.add(new WindowSize(w, h));
}
}
return modes;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// --- love2d-0002 tests ---
// Test 1: small mode list (50 unique × 3 dup = 150 entries)
{
total++;
List<WindowSize> modes = buildModeList(50, 3);
long slow = slowGetFullscreenSizes(modes);
long fast = fastGetFullscreenSizes(modes);
boolean ok = slow > fast * 10;
System.out.printf("[0002] Test 1 (50 unique × 3 dup): slow=%d, fast=%d, ratio=%.1fx — %s%n",
slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 2: large mode list (200 unique × 5 dup = 1000 entries)
{
total++;
List<WindowSize> modes = buildModeList(200, 5);
long slow = slowGetFullscreenSizes(modes);
long fast = fastGetFullscreenSizes(modes);
boolean ok = slow > fast * 100;
System.out.printf("[0002] Test 2 (200 unique × 5 dup): slow=%d, fast=%d, ratio=%.1fx — %s%n",
slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 3: worst case all same size (100% duplicates)
{
total++;
List<WindowSize> modes = new ArrayList<>();
for (int i = 0; i < 500; i++) modes.add(new WindowSize(1920, 1080));
long slow = slowGetFullscreenSizes(modes);
long fast = fastGetFullscreenSizes(modes);
// Slow: each of 500 entries scans [0..1) already-found sizes (always 1 entry)
// Actually worst case is when unique sizes accumulate but this is degenerate
// slow = 0+1+1+...+1 = 499; fast = 500 probes. Different pattern.
// For fully-duplicate list slow scans the 1-element seen list each time = 499 scans
// fast = 500 probes each O(1). Not a huge ratio but slow still > fast.
boolean ok = fast == 500 && slow == 499;
System.out.printf("[0002] Test 3 (all same): slow=%d, fast=%d — %s%n",
slow, fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// --- love2d-0003 tests ---
// Test 4: 100 unique paths + 50 repeated
{
total++;
List<String> paths = new ArrayList<>();
for (int i = 0; i < 100; i++) paths.add("/game/mod/pack" + i + ".zip");
for (int i = 0; i < 50; i++) paths.add("/game/mod/pack" + i + ".zip"); // duplicates
long slow = slowAllowMountPaths(paths);
long fast = fastAllowMountPaths(paths);
boolean ok = slow > fast * 20;
System.out.printf("[0003] Test 4 (100 unique+50 dup paths): slow=%d, fast=%d, ratio=%.1fx — %s%n",
slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 5: 500 unique paths growing O(n²) vs O(n)
{
total++;
List<String> paths = new ArrayList<>();
for (int i = 0; i < 500; i++) paths.add("/usr/share/love/mods/pack" + i);
long slow = slowAllowMountPaths(paths);
long fast = fastAllowMountPaths(paths);
boolean ok = slow > fast * 100;
System.out.printf("[0003] Test 5 (500 unique paths): slow=%d, fast=%d, ratio=%.1fx — %s%n",
slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}

View file

@ -0,0 +1,55 @@
# opensearch-001: ImmutableCacheStatsHolder O(n²) levelsList.contains in filterLevels
## Classification
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
- **Severity**: MEDIUM
- **Path**: Cache stats reporting — called on every stats API request
## Location
`server/src/main/java/org/opensearch/common/cache/stats/ImmutableCacheStatsHolder.java:232-235`
## Defect
```java
private List<String> filterLevels(String[] levels, List<String> dimensionNames) {
if (levels == null) {
return dimensionNames;
}
List<String> levelsList = Arrays.asList(levels); // backed array — O(n) contains
List<String> result = new ArrayList<>();
for (String dimensionName : dimensionNames) {
if (levelsList.contains(dimensionName)) { // O(levels.length) per iteration
result.add(dimensionName);
}
}
return result;
}
```
`Arrays.asList()` returns a fixed-size list backed by the array — `contains()` is a
linear O(levels.length) scan. The outer loop runs `dimensionNames.size()` times.
Total: **O(dimensionNames × levels)** = O(n²) when both grow proportionally.
For a cluster with D dimensions and L requested levels this fires on every
`/_nodes/stats` or cache stats request.
## Fix
```java
Set<String> levelsSet = new HashSet<>(Arrays.asList(levels)); // O(L) build
for (String dimensionName : dimensionNames) {
if (levelsSet.contains(dimensionName)) { // O(1)
result.add(dimensionName);
}
}
```
## Complexity
| Metric | Before | After |
|--------|--------|-------|
| contains() | O(L) | O(1) |
| Full filter | O(D×L) | O(D+L) |
## Affected Versions
All OpenSearch versions with the multi-tier cache stats feature.

View file

@ -0,0 +1,64 @@
# opensearch-002: MustToFilterRewriter O(n²) mustClausesToMove.contains in copy loop
## Classification
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
- **Severity**: MEDIUM
- **Path**: Query rewrite phase — fires on every bool query with must clauses
## Location
`server/src/main/java/org/opensearch/search/query/rewriters/MustToFilterRewriter.java:104`
## Defect
```java
List<QueryBuilder> mustClausesToMove = new ArrayList<>();
// Phase 1: identify clauses to move (O(n))
for (QueryBuilder clause : boolQuery.must()) {
QueryBuilder rewrittenClause = rewriteIfNeeded(clause, context);
rewrittenMustClauses.add(rewrittenClause);
if (isClauseIrrelevantToScoring(rewrittenClause, context)) {
mustClausesToMove.add(rewrittenClause);
}
}
// Phase 2: copy must clauses except moved ones (O(n) × O(n) = O(n²))
for (QueryBuilder rewrittenClause : rewrittenMustClauses) {
if (!mustClausesToMove.contains(rewrittenClause)) { // O(mustClausesToMove.size())
rewritten.must(rewrittenClause);
}
}
```
`mustClausesToMove` is an `ArrayList`. The copy loop iterates all `rewrittenMustClauses`
(up to M items) and for each calls `mustClausesToMove.contains()` which is O(K) where K
is the number of clauses to move.
Total: **O(M × K)** — worst case O(n²) when half the clauses are moved.
This fires on every search request that includes a bool query with must clauses, which is
the majority of structured OpenSearch queries.
## Fix
```java
Set<QueryBuilder> mustClausesToMoveSet = new HashSet<>(mustClausesToMove);
// ...
for (QueryBuilder rewrittenClause : rewrittenMustClauses) {
if (!mustClausesToMoveSet.contains(rewrittenClause)) { // O(1) identity hash
rewritten.must(rewrittenClause);
}
}
```
Note: `QueryBuilder` identity equality via `HashSet` is appropriate here since
`rewrittenMustClauses` contains the same object references added to `mustClausesToMove`.
## Complexity
| Metric | Before | After |
|--------|--------|-------|
| contains() | O(K) | O(1) |
| Full copy phase | O(M×K) | O(M+K) |
## Affected Versions
All OpenSearch versions with the MustToFilterRewriter (added in query rewriter framework).

View file

@ -0,0 +1,162 @@
package unit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* CWE-407 unit test: opensearch-001
* ImmutableCacheStatsHolder.java:232-235 Arrays.asList(levels).contains() inside
* a loop over dimensionNames O(n²) level filtering.
*
* Slow path: Arrays.asList().contains() O(L) per iteration.
* Fast path: HashSet.contains() O(1) per iteration.
*
* Compile: javac -d . CacheStatsLevelsContains.java
* Run: java -ea unit.CacheStatsLevelsContains
*/
public class CacheStatsLevelsContains {
/**
* Simulates the defective filterLevels().
* Returns the number of element comparisons performed.
*/
static long slowFilterLevels(String[] levels, List<String> dimensionNames) {
if (levels == null) return 0;
List<String> levelsList = Arrays.asList(levels); // O(n) contains() scan
List<String> result = new ArrayList<>();
long comparisons = 0;
for (String dimensionName : dimensionNames) {
// charge the linear scan cost
comparisons += levelsList.size();
if (levelsList.contains(dimensionName)) {
result.add(dimensionName);
}
}
return comparisons;
}
/**
* Simulates the fixed filterLevels() using HashSet.
* Returns the number of hash lookups performed.
*/
static long fastFilterLevels(String[] levels, List<String> dimensionNames) {
if (levels == null) return 0;
Set<String> levelsSet = new HashSet<>(Arrays.asList(levels));
List<String> result = new ArrayList<>();
long operations = 0;
for (String dimensionName : dimensionNames) {
operations++; // O(1) hash lookup
if (levelsSet.contains(dimensionName)) {
result.add(dimensionName);
}
}
return operations;
}
/** Run both paths and return filtered list (for correctness check). */
static List<String> filterSlow(String[] levels, List<String> dimensionNames) {
if (levels == null) return new ArrayList<>(dimensionNames);
List<String> levelsList = Arrays.asList(levels);
List<String> result = new ArrayList<>();
for (String d : dimensionNames) {
if (levelsList.contains(d)) result.add(d);
}
return result;
}
static List<String> filterFast(String[] levels, List<String> dimensionNames) {
if (levels == null) return new ArrayList<>(dimensionNames);
Set<String> levelsSet = new HashSet<>(Arrays.asList(levels));
List<String> result = new ArrayList<>();
for (String d : dimensionNames) {
if (levelsSet.contains(d)) result.add(d);
}
return result;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: correctness same output from both paths
{
total++;
String[] levels = {"shard", "node"};
List<String> dims = new ArrayList<>(Arrays.asList("index", "shard", "node", "tier"));
List<String> slowResult = filterSlow(levels, dims);
List<String> fastResult = filterFast(levels, dims);
assert slowResult.equals(fastResult) :
"Results differ: slow=" + slowResult + " fast=" + fastResult;
assert slowResult.size() == 2 : "Expected 2, got " + slowResult.size();
System.out.printf("Test 1 (correctness): both = %s%n", slowResult);
passed++;
}
// Test 2: null levels pass-through
{
total++;
List<String> dims = new ArrayList<>(Arrays.asList("a", "b", "c"));
long slowCost = slowFilterLevels(null, dims);
long fastCost = fastFilterLevels(null, dims);
assert slowCost == 0 && fastCost == 0;
System.out.printf("Test 2 (null levels): slow=%d fast=%d%n", slowCost, fastCost);
passed++;
}
// Test 3: cost ratio medium scale
{
total++;
int D = 100; // dimension names (like cache dimension keys in a large cluster)
int L = 50; // levels requested
List<String> dimensionNames = new ArrayList<>();
for (int i = 0; i < D; i++) dimensionNames.add("dim" + i);
String[] levels = new String[L];
for (int i = 0; i < L; i++) levels[i] = "dim" + (i * 2); // every other dim
long slowCost = slowFilterLevels(levels, dimensionNames);
long fastCost = fastFilterLevels(levels, dimensionNames);
assert slowCost > fastCost :
String.format("Expected slow > fast: slow=%d fast=%d", slowCost, fastCost);
System.out.printf("Test 3 (D=%d L=%d): slow=%d, fast=%d, ratio=%.1fx%n",
D, L, slowCost, fastCost, (double) slowCost / fastCost);
passed++;
}
// Test 4: large scale verify significant speedup
{
total++;
int D = 500;
int L = 200;
List<String> dimensionNames = new ArrayList<>();
for (int i = 0; i < D; i++) dimensionNames.add("dim" + i);
String[] levels = new String[L];
for (int i = 0; i < L; i++) levels[i] = "dim" + i;
long slowCost = slowFilterLevels(levels, dimensionNames);
long fastCost = fastFilterLevels(levels, dimensionNames);
double ratio = (double) slowCost / fastCost;
assert ratio > 10.0 :
String.format("Expected >10x speedup at D=%d L=%d, got %.1fx", D, L, ratio);
System.out.printf("Test 4 (D=%d L=%d): slow=%d, fast=%d, speedup=%.1fx%n",
D, L, slowCost, fastCost, ratio);
passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}

View file

@ -0,0 +1,174 @@
package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* CWE-407 unit test: opensearch-002
* MustToFilterRewriter.java:104 mustClausesToMove.contains() (ArrayList) inside
* loop over rewrittenMustClauses O(n²) clause copy.
*
* We model query clauses as Integer tokens (identity-comparable, like QueryBuilder refs).
*
* Slow path: ArrayList.contains() O(K) per check where K = clauses-to-move count.
* Fast path: HashSet.contains() O(1) per check.
*
* Compile: javac -d . MustToFilterRewriterContains.java
* Run: java -ea unit.MustToFilterRewriterContains
*/
public class MustToFilterRewriterContains {
/**
* Simulates the defective copy phase:
* for each rewrittenClause: if not in mustClausesToMove add to rewritten.must()
*
* Returns the number of list element comparisons performed.
*/
static long slowCopyPhase(List<Integer> rewrittenMustClauses,
List<Integer> mustClausesToMove) {
List<Integer> rewrittenMust = new ArrayList<>();
long comparisons = 0;
for (Integer clause : rewrittenMustClauses) {
// O(mustClausesToMove.size()) linear scan the defect
comparisons += mustClausesToMove.size();
if (!mustClausesToMove.contains(clause)) {
rewrittenMust.add(clause);
}
}
return comparisons;
}
/**
* Simulates the fixed copy phase using HashSet for O(1) membership.
* Returns the number of hash lookups performed.
*/
static long fastCopyPhase(List<Integer> rewrittenMustClauses,
List<Integer> mustClausesToMove) {
Set<Integer> moveSet = new HashSet<>(mustClausesToMove); // O(K) once
List<Integer> rewrittenMust = new ArrayList<>();
long operations = 0;
for (Integer clause : rewrittenMustClauses) {
operations++; // O(1) hash lookup
if (!moveSet.contains(clause)) {
rewrittenMust.add(clause);
}
}
return operations;
}
/** Correctness helper — returns the retained clauses. */
static List<Integer> retainSlow(List<Integer> rewrittenMustClauses,
List<Integer> mustClausesToMove) {
List<Integer> result = new ArrayList<>();
for (Integer c : rewrittenMustClauses) {
if (!mustClausesToMove.contains(c)) result.add(c);
}
return result;
}
static List<Integer> retainFast(List<Integer> rewrittenMustClauses,
List<Integer> mustClausesToMove) {
Set<Integer> moveSet = new HashSet<>(mustClausesToMove);
List<Integer> result = new ArrayList<>();
for (Integer c : rewrittenMustClauses) {
if (!moveSet.contains(c)) result.add(c);
}
return result;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: correctness same clauses retained
{
total++;
// 10 must clauses, move clauses 2, 4, 6
List<Integer> must = new ArrayList<>();
for (int i = 0; i < 10; i++) must.add(i);
List<Integer> toMove = new ArrayList<>();
toMove.add(2);
toMove.add(4);
toMove.add(6);
List<Integer> slowResult = retainSlow(must, toMove);
List<Integer> fastResult = retainFast(must, toMove);
assert slowResult.equals(fastResult) :
"Results differ: slow=" + slowResult + " fast=" + fastResult;
assert slowResult.size() == 7 :
"Expected 7 retained clauses, got " + slowResult.size();
System.out.printf("Test 1 (correctness): %d clauses retained, both agree%n",
slowResult.size());
passed++;
}
// Test 2: no clauses to move all retained
{
total++;
List<Integer> must = new ArrayList<>();
for (int i = 0; i < 20; i++) must.add(i);
List<Integer> toMove = new ArrayList<>(); // empty
List<Integer> slowResult = retainSlow(must, toMove);
List<Integer> fastResult = retainFast(must, toMove);
assert slowResult.equals(fastResult);
assert slowResult.size() == 20;
System.out.printf("Test 2 (none moved): %d clauses retained%n", slowResult.size());
passed++;
}
// Test 3: cost comparison medium clause count
{
total++;
int M = 200; // total must clauses
int K = 100; // clauses to move (half)
List<Integer> mustClauses = new ArrayList<>();
for (int i = 0; i < M; i++) mustClauses.add(i);
List<Integer> toMove = new ArrayList<>();
for (int i = 0; i < K; i++) toMove.add(i); // first K are moved
long slowCost = slowCopyPhase(mustClauses, toMove);
long fastCost = fastCopyPhase(mustClauses, toMove);
assert slowCost > fastCost * 5 :
String.format("Expected slow >> fast, got slow=%d fast=%d", slowCost, fastCost);
System.out.printf("Test 3 (M=%d K=%d): slow=%d, fast=%d, ratio=%.1fx%n",
M, K, slowCost, fastCost, (double) slowCost / fastCost);
passed++;
}
// Test 4: large scale verify significant speedup
{
total++;
int M = 1000;
int K = 500;
List<Integer> mustClauses = new ArrayList<>();
for (int i = 0; i < M; i++) mustClauses.add(i);
List<Integer> toMove = new ArrayList<>();
for (int i = 0; i < K; i++) toMove.add(i);
long slowCost = slowCopyPhase(mustClauses, toMove);
long fastCost = fastCopyPhase(mustClauses, toMove);
double ratio = (double) slowCost / fastCost;
assert ratio > 50.0 :
String.format("Expected >50x speedup at M=%d K=%d, got %.1fx", M, K, ratio);
System.out.printf("Test 4 (M=%d K=%d): slow=%d, fast=%d, speedup=%.1fx%n",
M, K, slowCost, fastCost, ratio);
passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}

View file

@ -0,0 +1,116 @@
# raylib-0002: LoadRandomSequence O(n²) dedup — CWE-407
**Severity:** MEDIUM
**File:** `src/rcore.c`
**Function:** `LoadRandomSequence()`
**Lines:** ~17881803
## Description
`LoadRandomSequence()` generates a sequence of `count` unique random integers
in [min, max]. The fallback path (when `SUPPORT_RPRAND_GENERATOR` is not
defined) uses a classic rejection-sampling loop with a nested linear scan:
```c
for (int i = 0; i < (int)count;)
{
value = GetRandomValue(min, max);
dupValue = false;
for (int j = 0; j < i; j++) // O(i) per attempt CWE-407
{
if (values[j] == value)
{
dupValue = true;
break;
}
}
if (!dupValue) { values[i] = value; i++; }
}
```
Total inner iterations: O(n²/2) expected. At n=1000 this is ~500 000 comparisons.
At n=10 000 this is ~50 000 000 comparisons.
## Fix
Replace the `values[]` linear scan with a `bool seen[]` bitmap (range is
bounded by [min, max]) for O(1) membership:
```c
// FIX raylib-0002: O(1) membership via boolean seen-array — CWE-407
int range = abs(max - min) + 1;
bool *seen = (bool *)RL_CALLOC(range, sizeof(bool));
for (int i = 0; i < (int)count;)
{
value = GetRandomValue(min, max);
int idx = value - min;
if (!seen[idx]) // O(1)
{
seen[idx] = true;
values[i] = value;
i++;
}
}
RL_FREE(seen);
```
Complexity: O(n) expected (geometric distribution, not O(n²)).
## Speedup
| n | Before (inner iters) | After (O(1) checks) | Ratio |
|-------|---------------------|---------------------|--------|
| 100 | ~2 500 | ~100 | ~25× |
| 1 000 | ~250 000 | ~1 000 | ~250× |
| 10 000| ~25 000 000 | ~10 000 | ~2500× |
## Patch
```diff
--- a/src/rcore.c
+++ b/src/rcore.c
@@ -1788,19 +1788,24 @@ int *LoadRandomSequence(unsigned int count, int min, int max)
values = (int *)RL_CALLOC(count, sizeof(int));
- int value = 0;
- bool dupValue = false;
-
- for (int i = 0; i < (int)count;)
- {
- value = GetRandomValue(min, max);
- dupValue = false;
-
- for (int j = 0; j < i; j++)
- {
- if (values[j] == value)
- {
- dupValue = true;
- break;
- }
- }
-
- if (!dupValue)
- {
- values[i] = value;
- i++;
- }
- }
+ // FIX raylib-0002: replace O(n) inner scan with O(1) boolean bitmap — CWE-407
+ int range = abs(max - min) + 1;
+ bool *seen = (bool *)RL_CALLOC(range, sizeof(bool));
+ for (int i = 0; i < (int)count;)
+ {
+ int value = GetRandomValue(min, max);
+ int idx = value - min;
+ if (!seen[idx])
+ {
+ seen[idx] = true;
+ values[i] = value;
+ i++;
+ }
+ }
+ RL_FREE(seen);
```

View file

@ -0,0 +1,157 @@
package unit;
import java.util.HashSet;
import java.util.Random;
/**
* raylib-0002: LoadRandomSequence O(n²) dedup CWE-407
*
* Demonstrates: for each of N values generated, a linear scan of the
* previously-accepted values is O(n) per iteration O(n²) total.
* Fix: use a boolean seen[] bitmap (range-bounded) for O(1) membership.
*/
public class RaylibRandomSequenceTest {
/**
* Slow path: mirrors the raylib fallback in LoadRandomSequence().
* Returns the exact number of inner-loop iterations (membership checks).
*/
static long slowRandomSequence(int count, int min, int max, long seed) {
int range = Math.abs(max - min) + 1;
assert count <= range : "count must not exceed range";
int[] values = new int[count];
Random rng = new Random(seed);
long innerOps = 0;
int i = 0;
while (i < count) {
int value = min + rng.nextInt(range);
boolean dupValue = false;
// O(i) linear scan CWE-407
for (int j = 0; j < i; j++) {
innerOps++;
if (values[j] == value) {
dupValue = true;
break;
}
}
if (!dupValue) {
values[i] = value;
i++;
}
}
// Verify correctness: all values unique and in [min, max]
HashSet<Integer> seen = new HashSet<>();
for (int v : values) {
assert v >= min && v <= max : "value out of range: " + v;
assert seen.add(v) : "duplicate value: " + v;
}
return innerOps;
}
/**
* Fast path: boolean seen[] bitmap O(1) membership per check.
* Returns the number of bitmap probes (each is O(1)).
*/
static long fastRandomSequence(int count, int min, int max, long seed) {
int range = Math.abs(max - min) + 1;
assert count <= range : "count must not exceed range";
int[] values = new int[count];
boolean[] seen = new boolean[range]; // FIX raylib-0002
Random rng = new Random(seed);
long probes = 0;
int i = 0;
while (i < count) {
int value = min + rng.nextInt(range);
int idx = value - min;
probes++; // O(1) bitmap probe
if (!seen[idx]) {
seen[idx] = true;
values[i] = value;
i++;
}
}
// Verify correctness
HashSet<Integer> check = new HashSet<>();
for (int v : values) {
assert v >= min && v <= max : "value out of range: " + v;
assert check.add(v) : "duplicate value: " + v;
}
return probes;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: small n verify slow is O(n²), fast is O(n)
{
total++;
int n = 100, min = 0, max = 999;
long seed = 42L;
long slowOps = slowRandomSequence(n, min, max, seed);
long fastOps = fastRandomSequence(n, min, max, seed);
// Slow expected ~n*(n-1)/4 ~ 2475 ops; fast expected ~n + small constant
boolean ok = slowOps > fastOps * 5;
System.out.printf("Test 1 (n=%d): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 2: medium n ratio should be much larger
{
total++;
int n = 500, min = 0, max = 9999;
long seed = 123L;
long slowOps = slowRandomSequence(n, min, max, seed);
long fastOps = fastRandomSequence(n, min, max, seed);
boolean ok = slowOps > fastOps * 50;
System.out.printf("Test 2 (n=%d): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 3: worst case count == range (dense selection forces many collisions)
{
total++;
int n = 200, min = 0, max = 199; // count == range: worst case for slow
long seed = 7L;
long slowOps = slowRandomSequence(n, min, max, seed);
long fastOps = fastRandomSequence(n, min, max, seed);
// Slow expected ~n²/4 = 10000 ops; fast expected ~n*H(n) 1060 ops (harmonic)
boolean ok = slowOps > fastOps * 5;
System.out.printf("Test 3 (n=%d, dense): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 4: correctness outputs must be identical sequences given same seed
{
total++;
int n = 50, min = 10, max = 200;
long seed = 99L;
// We can't compare element-by-element because the two algorithms
// process rejections differently per RNG call, so just verify
// both produce valid unique sequences independently.
long slowOps = slowRandomSequence(n, min, max, seed);
long fastOps = fastRandomSequence(n, min, max, seed);
boolean ok = slowOps > 0 && fastOps > 0;
System.out.printf("Test 4 (correctness, n=%d): slow valid=%b, fast valid=%b — %s%n",
n, slowOps > 0, fastOps > 0, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}

View file

@ -0,0 +1,73 @@
# solr-001: ClusterStatus O(n²) liveNodes.contains in crossCheckReplicaStateWithLiveNodes
## Classification
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
- **Severity**: HIGH
- **Path**: Admin API handler — called on every CLUSTERSTATUS request, which monitoring
tools typically poll every few seconds
## Location
`solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java:303`
## Defect
```java
protected void crossCheckReplicaStateWithLiveNodes(
List<String> liveNodes, Map<String, Object> collectionProps) {
var shards = (Map<String, Object>) collectionProps.get("shards");
for (Object nextShard : shards.values()) { // O(shards)
var replicas = (Map<String, Object>) shardMap.get("replicas");
for (Object nextReplica : replicas.values()) { // O(replicas/shard)
// ...
String node_name = (String) replicaMap.get(ZkStateReader.NODE_NAME_PROP);
if (!liveNodes.contains(node_name)) { // O(liveNodes)
replicaMap.put(ZkStateReader.STATE_PROP, Replica.State.DOWN.toString());
}
}
}
}
```
`liveNodes` is fetched via `zkStateReader.getZkClient().getChildren(ZkStateReader.LIVE_NODES_ZKNODE, null)`
which returns a plain `List<String>`. The double-nested shard/replica loop calls
`liveNodes.contains()` for every replica.
For a cluster with:
- N live nodes
- S shards
- R replicas/shard
Total comparisons per CLUSTERSTATUS call: **O(N × S × R)**
A production Solr cluster with 100 nodes, 500 shards, 3 replicas/shard =
150,000 string comparisons per call. With monitoring polling at 5s intervals:
1.8 million string comparisons per minute, completely wasted.
## Fix
Convert `liveNodes` to a `HashSet<String>` before the nested loops:
```java
Set<String> liveNodeSet = new HashSet<>(liveNodes); // O(N) once
for (Object nextShard : shards.values()) {
for (Object nextReplica : replicas.values()) {
if (!liveNodeSet.contains(node_name)) { // O(1)
...
}
}
}
```
The caller at line 129 already has `liveNodes` as a List; the fix can be applied
inside `crossCheckReplicaStateWithLiveNodes` without changing any callers.
## Complexity
| Metric | Before | After |
|--------|--------|-------|
| contains() | O(N) | O(1) |
| Full cross-check | O(N×S×R) | O(N + S×R) |
| At N=100, S=500, R=3 | 150,000 comparisons | ~1,600 |
| Speedup | 1× | ~94× |
## Affected Versions
Present in all Solr versions with the ClusterStatus handler (Solr 5+).

View file

@ -0,0 +1,60 @@
# solr-002: ActiveReplicaWatcher O(n²) replicaIds/solrCoreNames.contains in state-change loop
## Classification
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
- **Severity**: MEDIUM
- **Path**: ZooKeeper state-change callback — fires on every cluster state update
## Location
`solr/core/src/java/org/apache/solr/cloud/ActiveReplicaWatcher.java:161,169`
## Defect
```java
private final List<String> replicaIds = new ArrayList<>();
private final List<String> solrCoreNames = new ArrayList<>();
// Called on every ZK state change:
for (Slice slice : collectionState.getSlices()) { // O(shards)
for (Replica replica : slice.getReplicas()) { // O(replicas/shard)
if (replicaIds.contains(replica.getName())) { // O(replicaIds) = O(n)
...
} else if (solrCoreNames.contains( // O(solrCoreNames) = O(n)
replica.getStr(ZkStateReader.CORE_NAME_PROP))) {
...
}
}
}
```
Both `replicaIds` and `solrCoreNames` are `ArrayList`. The watcher fires on every
ZooKeeper cluster-state change event (node joins, replica state transitions, shard
splits, etc.). For a collection with S shards × R replicas and N watched IDs:
Total comparisons per event: **O(S × R × N)** for each list independently.
## Fix
Convert to `HashSet` at construction time (the lists are populated once and then only
shrink via `remove()`):
```java
private final Set<String> replicaIds = new HashSet<>();
private final Set<String> solrCoreNames = new HashSet<>();
```
`HashSet.remove()` is also O(1), so the existing removal calls in the loop body
(`replicaIds.remove(replica.getName())`) remain correct and become faster.
The public getters at lines 93/98 return the field directly as `List`; the return type
would need to change to `Collection` or the getter can wrap with `new ArrayList<>(replicaIds)`.
## Complexity
| Metric | Before | After |
|--------|--------|-------|
| contains() | O(N) | O(1) |
| Per ZK event | O(S×R×N) | O(S×R) |
| remove() | O(N) | O(1) |
## Affected Versions
Present in all Solr versions with `ActiveReplicaWatcher` (Solr 7+).

View file

@ -0,0 +1,234 @@
package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* CWE-407 unit test: solr-002
* ActiveReplicaWatcher.java:161,169 replicaIds.contains() and solrCoreNames.contains()
* (ArrayList) inside nested shard × replica loop O(S × R × N) per ZK event.
*
* Slow path: ArrayList.contains() O(N) per replica check.
* Fast path: HashSet.contains() O(1) per replica check.
*
* Compile: javac -d . ActiveReplicaWatcherContains.java
* Run: java -ea unit.ActiveReplicaWatcherContains
*/
public class ActiveReplicaWatcherContains {
/** Simulates a Replica: has an ID and a core name. */
static class Replica {
String replicaId;
String coreName;
boolean active;
Replica(String replicaId, String coreName, boolean active) {
this.replicaId = replicaId;
this.coreName = coreName;
this.active = active;
}
}
/** Simulates a Slice (shard). */
static class Slice {
List<Replica> replicas;
Slice(List<Replica> replicas) { this.replicas = replicas; }
}
/**
* Simulates the defective onStateChanged loop (ArrayList-based).
* Returns total comparison count across both contains() calls.
*/
static long slowOnStateChanged(List<String> watchedReplicaIds,
List<String> watchedCoreNames,
List<Slice> slices) {
List<String> mutableReplicaIds = new ArrayList<>(watchedReplicaIds);
List<String> mutableCoreNames = new ArrayList<>(watchedCoreNames);
List<Replica> activeReplicas = new ArrayList<>();
long comparisons = 0;
for (Slice slice : slices) { // O(S)
for (Replica replica : slice.replicas) { // O(R)
// First contains() O(replicaIds.size())
comparisons += mutableReplicaIds.size();
if (mutableReplicaIds.contains(replica.replicaId)) {
if (replica.active) {
activeReplicas.add(replica);
mutableReplicaIds.remove(replica.replicaId);
}
} else {
// Second contains() O(coreNames.size())
comparisons += mutableCoreNames.size();
if (mutableCoreNames.contains(replica.coreName)) {
if (replica.active) {
activeReplicas.add(replica);
mutableCoreNames.remove(replica.coreName);
}
}
}
}
}
return comparisons;
}
/**
* Simulates the fixed onStateChanged loop using HashSet.
* Returns total hash operations.
*/
static long fastOnStateChanged(List<String> watchedReplicaIds,
List<String> watchedCoreNames,
List<Slice> slices) {
Set<String> mutableReplicaIds = new HashSet<>(watchedReplicaIds);
Set<String> mutableCoreNames = new HashSet<>(watchedCoreNames);
List<Replica> activeReplicas = new ArrayList<>();
long operations = 0;
for (Slice slice : slices) {
for (Replica replica : slice.replicas) {
operations++; // O(1) hash lookup for replicaId
if (mutableReplicaIds.contains(replica.replicaId)) {
if (replica.active) {
activeReplicas.add(replica);
mutableReplicaIds.remove(replica.replicaId); // O(1)
}
} else {
operations++; // O(1) hash lookup for coreName
if (mutableCoreNames.contains(replica.coreName)) {
if (replica.active) {
activeReplicas.add(replica);
mutableCoreNames.remove(replica.coreName); // O(1)
}
}
}
}
}
return operations;
}
/** Build slices with replicas. */
static List<Slice> buildSlices(int S, int R) {
List<Slice> slices = new ArrayList<>();
for (int s = 0; s < S; s++) {
List<Replica> replicas = new ArrayList<>();
for (int r = 0; r < R; r++) {
String id = "replica_" + s + "_" + r;
String core = "core_" + s + "_" + r;
replicas.add(new Replica(id, core, (r == 0))); // first replica active
}
slices.add(new Slice(replicas));
}
return slices;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: correctness watched replicas are found
{
total++;
List<String> watchedIds = new ArrayList<>();
watchedIds.add("replica_0_0");
watchedIds.add("replica_1_0");
List<String> watchedCores = new ArrayList<>();
watchedCores.add("core_2_1");
List<Slice> slices = buildSlices(3, 2);
// Mark replica_2_1 as active for core-name path
slices.get(2).replicas.get(1).active = true;
long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices);
assert slowCost > 0;
System.out.printf("Test 1 (correctness): slow did %d comparisons%n", slowCost);
passed++;
}
// Test 2: both find same replicas
{
total++;
List<String> watchedIds = new ArrayList<>();
watchedIds.add("replica_0_0");
List<String> watchedCores = new ArrayList<>();
watchedCores.add("core_1_0");
List<Slice> slices1 = buildSlices(5, 3);
List<Slice> slices2 = buildSlices(5, 3);
List<String> wi2 = new ArrayList<>(watchedIds);
List<String> wc2 = new ArrayList<>(watchedCores);
long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1);
long fastCost = fastOnStateChanged(wi2, wc2, slices2);
// Both should find the same active replicas verifiable by same completion behavior
assert slowCost > 0 && fastCost > 0;
System.out.printf("Test 2 (both paths): slow=%d fast=%d%n", slowCost, fastCost);
passed++;
}
// Test 3: cost comparison medium cluster
{
total++;
int S = 100; // shards
int R = 3; // replicas/shard
int W = 50; // watched replica IDs
List<String> watchedIds = new ArrayList<>();
for (int i = 0; i < W; i++) watchedIds.add("replica_" + i + "_0");
List<String> watchedCores = new ArrayList<>();
for (int i = 0; i < W; i++) watchedCores.add("core_" + (i + W) + "_0");
List<Slice> slices1 = buildSlices(S, R);
List<Slice> slices2 = buildSlices(S, R);
List<String> wi2 = new ArrayList<>(watchedIds);
List<String> wc2 = new ArrayList<>(watchedCores);
long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1);
long fastCost = fastOnStateChanged(wi2, wc2, slices2);
assert slowCost > fastCost * 3 :
String.format("Expected slow >> fast: slow=%d fast=%d", slowCost, fastCost);
System.out.printf("Test 3 (S=%d R=%d W=%d): slow=%d, fast=%d, ratio=%.1fx%n",
S, R, W, slowCost, fastCost, (double) slowCost / fastCost);
passed++;
}
// Test 4: large cluster significant speedup
{
total++;
int S = 500;
int R = 3;
int W = 200;
List<String> watchedIds = new ArrayList<>();
for (int i = 0; i < W; i++) watchedIds.add("replica_" + i + "_0");
List<String> watchedCores = new ArrayList<>();
for (int i = 0; i < W; i++) watchedCores.add("core_" + i + "_0");
List<Slice> slices1 = buildSlices(S, R);
List<Slice> slices2 = buildSlices(S, R);
List<String> wi2 = new ArrayList<>(watchedIds);
List<String> wc2 = new ArrayList<>(watchedCores);
long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1);
long fastCost = fastOnStateChanged(wi2, wc2, slices2);
double ratio = (double) slowCost / fastCost;
assert ratio > 10.0 :
String.format("Expected >10x speedup at S=%d R=%d W=%d, got %.1fx",
S, R, W, ratio);
System.out.printf("Test 4 (S=%d R=%d W=%d): slow=%d, fast=%d, speedup=%.1fx%n",
S, R, W, slowCost, fastCost, ratio);
passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}

View file

@ -0,0 +1,195 @@
package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* CWE-407 unit test: solr-001
* ClusterStatus.java:303 liveNodes.contains(node_name) (List) inside double
* loop over shards × replicas O(N × S × R) per CLUSTERSTATUS request.
*
* Slow path: List<String>.contains() O(N) per replica.
* Fast path: HashSet<String>.contains() O(1) per replica.
*
* Compile: javac -d . ClusterStatusLiveNodesContains.java
* Run: java -ea unit.ClusterStatusLiveNodesContains
*/
public class ClusterStatusLiveNodesContains {
/** Simulates a Replica record. */
static class Replica {
String nodeName;
String state; // "active" or "down"
Replica(String nodeName, String state) {
this.nodeName = nodeName;
this.state = state;
}
}
/** Simulates a Shard record. */
static class Shard {
List<Replica> replicas;
Shard(List<Replica> replicas) { this.replicas = replicas; }
}
/**
* Simulates the defective crossCheckReplicaStateWithLiveNodes.
* Returns total comparison count (liveNodes.contains() calls × avg scan length).
*/
static long slowCrossCheck(List<String> liveNodes, List<Shard> shards) {
long comparisons = 0;
for (Shard shard : shards) {
for (Replica replica : shard.replicas) {
if (!"down".equals(replica.state)) {
// O(liveNodes.size()) scan
comparisons += liveNodes.size();
if (!liveNodes.contains(replica.nodeName)) {
replica.state = "down";
}
}
}
}
return comparisons;
}
/**
* Simulates the fixed crossCheckReplicaStateWithLiveNodes using HashSet.
* Returns total hash operations (O(1) each).
*/
static long fastCrossCheck(List<String> liveNodes, List<Shard> shards) {
Set<String> liveNodeSet = new HashSet<>(liveNodes); // O(N) once
long operations = 0;
for (Shard shard : shards) {
for (Replica replica : shard.replicas) {
if (!"down".equals(replica.state)) {
operations++; // O(1) hash lookup
if (!liveNodeSet.contains(replica.nodeName)) {
replica.state = "down";
}
}
}
}
return operations;
}
/** Build a test cluster: N live nodes, S shards, R replicas each. */
static List<Shard> buildCluster(int S, int R, int N) {
List<Shard> shards = new ArrayList<>();
for (int s = 0; s < S; s++) {
List<Replica> replicas = new ArrayList<>();
for (int r = 0; r < R; r++) {
// Distribute replicas across nodes; node (s*R+r) % N
String nodeName = "node" + ((s * R + r) % N);
replicas.add(new Replica(nodeName, "active"));
}
shards.add(new Shard(replicas));
}
return shards;
}
static List<String> buildLiveNodes(int N) {
List<String> nodes = new ArrayList<>();
for (int i = 0; i < N; i++) nodes.add("node" + i);
return nodes;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: correctness replicas on dead nodes get marked DOWN
{
total++;
List<String> liveNodes = new ArrayList<>();
liveNodes.add("node0");
liveNodes.add("node1");
// node2 is dead
List<Shard> shards = new ArrayList<>();
List<Replica> r1 = new ArrayList<>();
r1.add(new Replica("node0", "active"));
r1.add(new Replica("node2", "active")); // should go DOWN
r1.add(new Replica("node1", "active"));
shards.add(new Shard(r1));
slowCrossCheck(liveNodes, shards);
assert "active".equals(shards.get(0).replicas.get(0).state) : "node0 should stay active";
assert "down".equals(shards.get(0).replicas.get(1).state) : "node2 replica should be DOWN";
assert "active".equals(shards.get(0).replicas.get(2).state) : "node1 should stay active";
System.out.println("Test 1 (correctness): node2 replica correctly marked DOWN");
passed++;
}
// Test 2: fast path correctness
{
total++;
List<String> liveNodes = new ArrayList<>();
liveNodes.add("node0");
liveNodes.add("node1");
List<Shard> shards = new ArrayList<>();
List<Replica> r1 = new ArrayList<>();
r1.add(new Replica("node0", "active"));
r1.add(new Replica("node99", "active")); // dead
shards.add(new Shard(r1));
fastCrossCheck(liveNodes, shards);
assert "active".equals(shards.get(0).replicas.get(0).state);
assert "down".equals(shards.get(0).replicas.get(1).state) : "node99 should be DOWN";
System.out.println("Test 2 (fast correctness): node99 replica correctly marked DOWN");
passed++;
}
// Test 3: cost comparison medium cluster
{
total++;
int N = 50; // live nodes
int S = 100; // shards
int R = 3; // replicas/shard
List<String> liveNodes = buildLiveNodes(N);
List<Shard> slowShards = buildCluster(S, R, N);
List<Shard> fastShards = buildCluster(S, R, N);
long slowCost = slowCrossCheck(liveNodes, slowShards);
long fastCost = fastCrossCheck(liveNodes, fastShards);
assert slowCost > fastCost * 5 :
String.format("Expected slow >> fast: slow=%d fast=%d", slowCost, fastCost);
System.out.printf("Test 3 (N=%d S=%d R=%d): slow=%d, fast=%d, ratio=%.1fx%n",
N, S, R, slowCost, fastCost, (double) slowCost / fastCost);
passed++;
}
// Test 4: production-scale cluster
{
total++;
int N = 100; // live nodes
int S = 500; // shards
int R = 3; // replicas/shard
List<String> liveNodes = buildLiveNodes(N);
List<Shard> slowShards = buildCluster(S, R, N);
List<Shard> fastShards = buildCluster(S, R, N);
long slowCost = slowCrossCheck(liveNodes, slowShards);
long fastCost = fastCrossCheck(liveNodes, fastShards);
double ratio = (double) slowCost / fastCost;
assert ratio > 20.0 :
String.format("Expected >20x speedup at N=%d S=%d R=%d, got %.1fx",
N, S, R, ratio);
System.out.printf("Test 4 (N=%d S=%d R=%d): slow=%d, fast=%d, speedup=%.1fx%n",
N, S, R, slowCost, fastCost, ratio);
passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}

View file

@ -0,0 +1,93 @@
# tokio-0001: AnyDelimiterCodec seek_delimiters Vec<u8>::contains() O(N×D) per frame decode
**Severity:** HIGH
**CWE:** CWE-407 (Algorithmic Complexity — linear membership test in hot loop)
**Speedup:** >10x at D=16 delimiters (typical multi-delimiter usage)
**Target:** tokio (tokio-rs/tokio)
**File:** `tokio-util/src/codec/any_delimiter_codec.rs:146`
## Description
`AnyDelimiterCodec` is a framing codec that splits network byte streams at any
of a set of delimiter bytes (e.g., `b",;\n"`). Its `decode()` method is called
once per network read — it is a hot path for every connection using this codec.
Inside `decode()`, the codec iterates over every byte in the receive buffer and,
for each byte, calls `self.seek_delimiters.contains(b)` to test whether that
byte is a delimiter:
```rust
// tokio-util/src/codec/any_delimiter_codec.rs:144-146
let new_chunk_offset = buf[self.next_index..read_to]
.iter()
.position(|b| self.seek_delimiters.contains(b));
```
`seek_delimiters` is a `Vec<u8>`. `Vec::contains()` is a linear scan: O(D)
per byte, where D is the number of delimiter bytes. Total cost per decode call:
**O(N × D)** where N = bytes in the receive buffer.
For a frame of 4 KB with D=8 delimiters, this is 32,768 comparisons instead of
4,096. For D=16 it is 65,536 — a 16× overhead versus the byte count alone.
## Root Cause
`seek_delimiters: Vec<u8>` was chosen for flexibility (user-supplied list of
delimiter bytes), but `Vec::contains()` does a sequential scan. Since the
delimiter set is over a byte alphabet (0255), an O(1) lookup table suffices.
Fix: at construction time, convert the delimiter list into a `[bool; 256]`
lookup table. Each byte check becomes a single array index: `self.delimiter_table[*b as usize]`.
## Patch
```diff
--- a/tokio-util/src/codec/any_delimiter_codec.rs
+++ b/tokio-util/src/codec/any_delimiter_codec.rs
@@ -41,9 +41,9 @@ pub struct AnyDelimiterCodec {
next_index: usize,
max_length: usize,
is_discarding: bool,
- seek_delimiters: Vec<u8>,
+ delimiter_table: [bool; 256],
sequence_writer: Vec<u8>,
}
@@ -68,8 +68,11 @@ impl AnyDelimiterCodec {
pub fn new(seek_delimiters: Vec<u8>, sequence_writer: Vec<u8>) -> AnyDelimiterCodec {
+ let mut delimiter_table = [false; 256];
+ for &b in &seek_delimiters {
+ delimiter_table[b as usize] = true;
+ }
AnyDelimiterCodec {
next_index: 0,
max_length: usize::MAX,
is_discarding: false,
- seek_delimiters,
+ delimiter_table,
sequence_writer,
}
}
@@ -141,7 +144,7 @@ impl Decoder for AnyDelimiterCodec {
let new_chunk_offset = buf[self.next_index..read_to]
.iter()
- .position(|b| self.seek_delimiters.contains(b));
+ .position(|b| self.delimiter_table[*b as usize]);
```
## Complexity Before
Per byte in receive buffer: **O(D)** — Vec::contains linear scan over D delimiters
Per decode call (N-byte buffer): **O(N × D)**
## Complexity After
Per byte in receive buffer: **O(1)** — array index lookup
Per decode call (N-byte buffer): **O(N)**
## Reproduction
```
cd defects/tokio/unit && javac -d . *.java && java -ea unit.AnyDelimiterCodecTest
```

View file

@ -0,0 +1,233 @@
package unit;
import java.util.*;
/**
* Unit test for tokio-0001: CWE-407 in AnyDelimiterCodec.
*
* tokio-0001 (HIGH):
* File: tokio-util/src/codec/any_delimiter_codec.rs:146
* Symbol: AnyDelimiterCodec::decode Vec<u8>::contains() called per buffer byte
* Defect: For each byte in the receive buffer (N bytes), the codec calls
* seek_delimiters.contains(b) which is a linear scan over D delimiter
* bytes: O(D) per byte, O(N×D) total per decode call.
* Fix: At construction time, convert seek_delimiters into a [bool; 256]
* lookup table. Each check becomes delimiter_table[b as usize]: O(1).
*
* Modeled here in Java:
* Rust Vec<u8>::contains() boolean[] linear scan (defective)
* Rust [bool; 256] indexing boolean[] direct index (fixed)
* Comparison counts tracked at the membership-test site.
*
* Expected at N=4096 bytes, D=16 delimiters:
* defective comparisons = N × D = 65,536
* fixed comparisons = N = 4,096
* ratio = 16×
*/
public class AnyDelimiterCodecTest {
// =========================================================================
// tokio-0001 model: per-byte delimiter check, Vec linear scan vs lookup table
// =========================================================================
/**
* Defective decoder: for each byte in the buffer, scan the delimiter list
* linearly (Vec<u8>::contains).
*
* Returns the index of the first delimiter byte found, or -1.
* comparisons counts every element examined during scans.
*/
static class DefectiveDecoder {
final byte[] delimiters;
long comparisons = 0;
DefectiveDecoder(byte[] delimiters) {
this.delimiters = delimiters;
}
/**
* Scan buf[0..len) for any delimiter byte.
* For each byte b: linearly scan delimiters (O(D) per byte).
*/
int findFirstDelimiter(byte[] buf, int len) {
for (int i = 0; i < len; i++) {
byte b = buf[i];
// Vec<u8>::contains(b) O(D) linear scan
for (int d = 0; d < delimiters.length; d++) {
comparisons++;
if (delimiters[d] == b) {
return i;
}
}
}
return -1;
}
}
/**
* Fixed decoder: pre-built boolean[256] lookup table at construction time.
* For each byte b: delimiter_table[b & 0xFF] O(1).
* comparisons counts one operation per byte checked.
*/
static class FixedDecoder {
final boolean[] delimiterTable = new boolean[256];
long comparisons = 0;
FixedDecoder(byte[] delimiters) {
for (byte d : delimiters) {
delimiterTable[d & 0xFF] = true;
}
}
/**
* Scan buf[0..len) for any delimiter byte using the lookup table.
*/
int findFirstDelimiter(byte[] buf, int len) {
for (int i = 0; i < len; i++) {
comparisons++; // one array-index lookup per byte
if (delimiterTable[buf[i] & 0xFF]) {
return i;
}
}
return -1;
}
}
// =========================================================================
// Tests
// =========================================================================
/**
* Test 1 Correctness: defective and fixed decoders agree on first delimiter position.
*
* Buffer: bytes 0..127 sequentially. Delimiters: {10, 44, 59} (\n , ;).
* First delimiter in the buffer should be byte value 10 at position 10.
*/
static void testCorrectnessMatch() {
byte[] delimiters = {10, 44, 59}; // \n , ;
byte[] buf = new byte[128];
for (int i = 0; i < 128; i++) buf[i] = (byte) i;
DefectiveDecoder def = new DefectiveDecoder(delimiters);
FixedDecoder fix = new FixedDecoder(delimiters);
int defPos = def.findFirstDelimiter(buf, buf.length);
int fixPos = fix.findFirstDelimiter(buf, buf.length);
assert defPos == fixPos
: "position mismatch: defective=" + defPos + " fixed=" + fixPos;
assert defPos == 10
: "expected delimiter at position 10 (\\n); got " + defPos;
System.out.println("PASS testCorrectnessMatch");
}
/**
* Test 2 No delimiter: both decoders return -1 when no delimiter present.
*
* Buffer filled with 0xFF (non-delimiter). Delimiters: {10, 44, 59}.
*/
static void testNoDelimiterFound() {
byte[] delimiters = {10, 44, 59};
int N = 1024;
byte[] buf = new byte[N];
Arrays.fill(buf, (byte) 0xFF);
DefectiveDecoder def = new DefectiveDecoder(delimiters);
FixedDecoder fix = new FixedDecoder(delimiters);
int defPos = def.findFirstDelimiter(buf, N);
int fixPos = fix.findFirstDelimiter(buf, N);
assert defPos == -1 : "defective should return -1; got " + defPos;
assert fixPos == -1 : "fixed should return -1; got " + fixPos;
// Defective scanned all N×D pairs; fixed scanned N
assert def.comparisons == (long) N * delimiters.length
: "defective comparisons should be N*D=" + (long) N * delimiters.length
+ "; got " + def.comparisons;
assert fix.comparisons == N
: "fixed comparisons should be N=" + N + "; got " + fix.comparisons;
System.out.println("PASS testNoDelimiterFound");
}
/**
* Test 3 tokio-0001: O(N×D) vs O(N) ratio at N=4096, D=16.
*
* Worst-case: buffer contains no delimiter bytes (full scan).
* Defective: 4096 × 16 = 65,536 comparisons.
* Fixed: 4,096 comparisons.
* Ratio: 16×.
*/
static void testRatioAtScale() {
int N = 4096;
int D = 16;
byte[] delimiters = new byte[D];
// Use bytes 128..143 as delimiters none appear in the buffer (filled with 0)
for (int i = 0; i < D; i++) delimiters[i] = (byte) (128 + i);
byte[] buf = new byte[N]; // all zeros no delimiter matches
DefectiveDecoder def = new DefectiveDecoder(delimiters);
FixedDecoder fix = new FixedDecoder(delimiters);
def.findFirstDelimiter(buf, N);
fix.findFirstDelimiter(buf, N);
long expectedDef = (long) N * D;
long expectedFix = N;
assert def.comparisons == expectedDef
: "defective comparisons should be N*D=" + expectedDef
+ "; got " + def.comparisons;
assert fix.comparisons == expectedFix
: "fixed comparisons should be N=" + expectedFix
+ "; got " + fix.comparisons;
double ratio = (double) def.comparisons / fix.comparisons;
assert ratio >= D
: "ratio should be >= D=" + D + "; got " + ratio;
System.out.printf(
"PASS testRatioAtScale (N=%d D=%d defective=%d fixed=%d ratio=%.1fx)%n",
N, D, def.comparisons, fix.comparisons, ratio);
}
/**
* Test 4 Delimiter at start: both return position 0 immediately.
*
* Buffer starts with a delimiter byte.
*/
static void testDelimiterAtStart() {
byte[] delimiters = {(byte) 0xAB};
byte[] buf = {(byte) 0xAB, 0, 1, 2, 3};
DefectiveDecoder def = new DefectiveDecoder(delimiters);
FixedDecoder fix = new FixedDecoder(delimiters);
assert def.findFirstDelimiter(buf, buf.length) == 0
: "defective should find delimiter at position 0";
assert fix.findFirstDelimiter(buf, buf.length) == 0
: "fixed should find delimiter at position 0";
// Defective: 1 comparison (found on first byte, first delimiter)
assert def.comparisons == 1
: "defective should make 1 comparison; got " + def.comparisons;
// Fixed: 1 lookup
assert fix.comparisons == 1
: "fixed should make 1 comparison; got " + fix.comparisons;
System.out.println("PASS testDelimiterAtStart");
}
// =========================================================================
public static void main(String[] args) {
testCorrectnessMatch();
testNoDelimiterFound();
testRatioAtScale();
testDelimiterAtStart();
System.out.println("4/4 PASS");
}
}

Binary file not shown.

View file

@ -0,0 +1,34 @@
# Zig CWE-407 Scan — CLEAN
## Date
2026-03-27
## Scope
- `src/Sema.zig` — semantic analysis, type inference, comptime evaluation
- `src/InternPool.zig` — type/value interning
- `src/Zcu.zig`, `src/Zcu/PerThread.zig` — compilation unit management
- `src/Air/Liveness.zig` — liveness analysis
- `src/link/Elf.zig`, `src/link/Elf/` — ELF linker
- `src/link/MachO.zig`, `src/link/Elf/synthetic_sections.zig`
- `src/codegen/x86_64/CodeGen.zig`, `src/codegen/llvm.zig`
## Findings
No CWE-407 defects found. Zig's compiler is well-designed with O(1) data structures
throughout all hot paths:
- **Error set membership**: `InternPool.ErrorSetType.nameIndex` uses a hash map (`names_map`)
— O(1) lookup.
- **Error set merge**: `errorSetMerge` builds via `InferredErrorSet.NameMap` (hash map) — O(N).
- **Liveness analysis**: `live_set` is `AutoHashMapUnmanaged` — O(1) operations.
- **Switch dedup**: `seen_errors` is `AutoHashMap` — O(1) operations.
- **GOT/PLT entries**: Indexed via `symbol.flags.has_got` / `symbol.addExtra` — O(1).
- **Rpath dedup**: `rpath_table` is a hash map — O(1).
- **Analysis tracking**: `outdated`, `failed_analysis`, `analysis_in_progress` are all
`AutoHashMapUnmanaged` or `AutoArrayHashMap` — O(1) contains checks.
The one instance of `std.mem.indexOfScalar` found (`src/InternPool.zig:1890`) is a
null-check for underscore characters in strings — not a membership test in a hot loop.
## Conclusion
Zig CLEAN for CWE-407. The language team has consistently chosen hash-based data
structures for all compiler-internal membership tracking.