pygame/libgdx: CWE-407 findings

pygame: CLEAN — runtime uses dict/set throughout (O(1) membership)
libgdx-0002: ModelBuilder.rebuildReferences Array.contains O(P*M) — patch
libgdx-0005: Stage.touchDragged touchFocuses.contains O(F^2) — patch
libgdx-0006: AfterAction.delegate currentActions.indexOf O(W*A) per frame — patch
unit: extend LibGDXTest to 7 tests, all PASS (50x-1971x speedup measured)
This commit is contained in:
russell@unturf.com 2026-03-30 09:13:34 -04:00
parent ec395a01fc
commit 783e406633
11 changed files with 816 additions and 3 deletions

View file

@ -0,0 +1,76 @@
import java.util.*;
/**
* CWE-407 unit tests for Nomad new defects.
*
* nomad-0005: scheduler/reconciler/reconcile_cluster.go ~line 1337
* handleReconnectingAllocs() builds a []string replacements slice by following
* the NextAllocation linked list, then iterates all allocs with:
* slices.Contains(replacements, replacementAlloc.ID)
* This is O(|all| × |replacements|) = O(A × R).
* In a deployment with many reconnecting allocations and a large alloc set,
* this becomes effectively O(A²).
* Fix: map[string]struct{} built from the linked-list walk O(1) membership.
*
* Note: nomad-0001 through nomad-0004 (bitmap port alloc, stream namespace
* filter, vault secrets dedup, checkstore difference) were found in a prior
* scan and carry UNDF-2026-000000476 through UNDF-2026-000000479.
* nomad-0005 is the reconnect reconciler defect first identified here.
*/
public class NomadTest {
// --- nomad-0005 simulation ---
static boolean containsReplacements_slice(List<String> replacements, String allocID) {
return replacements.contains(allocID); // O(R) defect
}
static boolean containsReplacements_map(Set<String> replacementsSet, String allocID) {
return replacementsSet.contains(allocID); // O(1) fix
}
static void testNomad0005() throws Exception {
int A = 2000; // total allocations in a large deployment
int R = 500; // length of replacement chain per reconnecting alloc
List<String> replacementsSlice = new ArrayList<>(R);
Set<String> replacementsSet = new HashSet<>(R);
for (int i = 0; i < R; i++) {
String id = "alloc-replace-" + i;
replacementsSlice.add(id);
replacementsSet.add(id);
}
// alloc IDs that the outer loop iterates over all allocations
List<String> allAllocIDs = new ArrayList<>(A);
for (int i = 0; i < A; i++) allAllocIDs.add("alloc-" + i);
// worst-case candidate: last entry in replacements (tail of chain)
String targetID = "alloc-replace-" + (R - 1);
long t0 = System.nanoTime();
int hitSlice = 0;
for (String id : allAllocIDs) {
if (containsReplacements_slice(replacementsSlice, targetID)) hitSlice++;
}
long sliceNs = System.nanoTime() - t0;
long t1 = System.nanoTime();
int hitMap = 0;
for (String id : allAllocIDs) {
if (containsReplacements_map(replacementsSet, targetID)) hitMap++;
}
long mapNs = System.nanoTime() - t1;
if (hitSlice != hitMap) throw new AssertionError("result mismatch: " + hitSlice + " vs " + hitMap);
double ratio = (double) sliceNs / Math.max(mapNs, 1);
System.out.printf("nomad-0005 PASS A=%d R=%d slice=%dns map=%dns ratio=%.1fx%n",
A, R, sliceNs, mapNs, ratio);
if (ratio < 5.0) throw new AssertionError("expected speedup >= 5x, got " + ratio);
}
public static void main(String[] args) throws Exception {
testNomad0005();
System.out.println("ALL PASS");
}
}