whitepaper: re-add 10 missing entries + 11 new defects this session, count 578→590; rebuild PDF

This commit is contained in:
russell@unturf.com 2026-03-27 22:18:31 -04:00
parent e4ee168b1e
commit 2e4f7807d5
401 changed files with 3914 additions and 114 deletions

View file

@ -1,3 +1,4 @@
# UNDF: UNDF-2026-000000295
From 5708b73 Mon Sep 17 00:00:00 2001
Subject: [PATCH] CWE-407: spring-0001/0002 — fix O(B²) contains() in
mergeNamesWithParent and ImportStack

View file

@ -1,3 +1,4 @@
# UNDF: UNDF-2026-000000296
From 0000002 Mon Sep 17 00:00:00 2001
Subject: [PATCH] CWE-407: spring-0003/0004 — fix O(n²) ArrayList.contains()
in AbstractApplicationEventMulticaster

View file

@ -0,0 +1,203 @@
package unit;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Unit test for Spring CWE-407 defects:
* spring-0001: AnnotationTypeMapping.processAliases aliases.contains (ArrayList) in nested loops
* spring-0002: AbstractApplicationEventMulticaster allListeners.contains (ArrayList) in loop
*
* Op counting model: each contains() call costs as many ops as there are elements scanned.
* - ArrayList.contains(x): scans up to N elements N ops
* - HashSet.contains(x): O(1) 1 op
*
* No JUnit. No external deps. Compile and run:
* javac -d . *.java && java -ea unit.SpringTest
*/
public class SpringTest {
// ---- spring-0001 simulation ----
// Simulates processAliases: for each attribute (A), for each mapping depth (M),
// for each attribute, call aliases.contains O(A² × M) total inner scan ops
static long slowAliasProcessing(int attributeCount, int mappingDepth) {
long innerScanOps = 0;
List<Integer> aliases = new ArrayList<>();
for (int i = 0; i < attributeCount; i++) {
aliases.clear();
aliases.add(i);
// collectAliases: for each k, scan the ArrayList
for (int k = 0; k < attributeCount; k++) {
// ArrayList.contains scans all current elements
innerScanOps += aliases.size();
if (!aliases.contains(k)) {
aliases.add(k);
}
}
// processAliases(i, aliases): while(mapping depth) + for(each attribute)
for (int depth = 0; depth < mappingDepth; depth++) {
for (int j = 0; j < attributeCount; j++) {
// ArrayList.contains scans all aliases
innerScanOps += aliases.size();
// (don't call aliases.contains again cost already counted above)
}
}
}
return innerScanOps;
}
static long fastAliasProcessing(int attributeCount, int mappingDepth) {
long innerScanOps = 0;
Set<Integer> aliases = new LinkedHashSet<>();
for (int i = 0; i < attributeCount; i++) {
aliases.clear();
aliases.add(i);
// collectAliases: HashSet.contains is O(1) = 1 op per call
for (int k = 0; k < attributeCount; k++) {
innerScanOps += 1; // O(1) lookup
aliases.add(k); // Set.add handles dedup
}
// processAliases: HashSet.contains O(1) per call
for (int depth = 0; depth < mappingDepth; depth++) {
for (int j = 0; j < attributeCount; j++) {
innerScanOps += 1; // O(1)
}
}
}
return innerScanOps;
}
// ---- spring-0002 simulation ----
// Simulates retrieveApplicationListeners: for each listenerBean, call allListeners.contains.
// allListeners is ArrayList O(L) scan per call; three calls per bean.
static long slowListenerDedup(int listenerCount) {
long innerScanOps = 0;
List<Integer> allListeners = new ArrayList<>();
// Pre-populate with half the listeners (programmatic registration)
for (int i = 0; i < listenerCount / 2; i++) {
allListeners.add(i);
}
// Iterate bean-name listeners (the other half)
for (int i = listenerCount / 2; i < listenerCount; i++) {
// filteredListeners.contains(unwrappedListener) scan cost
innerScanOps += allListeners.size();
// allListeners.contains(unwrappedListener) scan cost
innerScanOps += allListeners.size();
// allListeners.contains(listener) scan cost
innerScanOps += allListeners.size();
if (!allListeners.contains(i)) {
allListeners.add(i);
}
}
return innerScanOps;
}
static long fastListenerDedup(int listenerCount) {
long innerScanOps = 0;
Set<Integer> allListeners = new LinkedHashSet<>();
for (int i = 0; i < listenerCount / 2; i++) {
allListeners.add(i);
}
for (int i = listenerCount / 2; i < listenerCount; i++) {
// Each contains on LinkedHashSet: O(1) = 1 op
innerScanOps += 1;
innerScanOps += 1;
innerScanOps += 1;
allListeners.add(i);
}
return innerScanOps;
}
public static void main(String[] args) {
int pass = 0;
int total = 0;
// --- spring-0001 tests ---
{
total++;
long slow = slowAliasProcessing(20, 5);
long fast = fastAliasProcessing(20, 5);
// With A=20, M=5: slow has O(A^2 * M) scan ops = ~2000+, fast has O(A * (A + A*M)) = ~2400
// The key difference is in collectAliases: slow scans growing list (avg A/2), fast is 1
boolean ok = slow > fast;
System.out.println("[spring-0001] A=20 M=5: slow_ops=" + slow + " fast_ops=" + fast +
" ratio=" + String.format("%.1f", (double)slow/Math.max(fast,1)) + "x " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
{
total++;
long slow = slowAliasProcessing(50, 10);
long fast = fastAliasProcessing(50, 10);
boolean ok = slow > fast * 3;
System.out.println("[spring-0001] A=50 M=10: slow_ops=" + slow + " fast_ops=" + fast +
" ratio=" + String.format("%.1f", (double)slow/Math.max(fast,1)) + "x " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
{
total++;
// Correctness: both paths discover same unique attributes
List<Integer> slowResult = new ArrayList<>();
Set<Integer> fastResult = new LinkedHashSet<>();
int A = 15;
for (int i = 0; i < A; i++) {
if (!slowResult.contains(i)) slowResult.add(i);
fastResult.add(i);
}
// Also add some duplicates
for (int i = 0; i < A / 2; i++) {
if (!slowResult.contains(i)) slowResult.add(i);
fastResult.add(i);
}
boolean ok = slowResult.size() == fastResult.size() &&
new ArrayList<>(fastResult).equals(slowResult);
System.out.println("[spring-0001] correctness A=15: slow_size=" + slowResult.size() +
" fast_size=" + fastResult.size() + " " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
// --- spring-0002 tests ---
{
total++;
long slow = slowListenerDedup(200);
long fast = fastListenerDedup(200);
boolean ok = slow > fast * 10;
System.out.println("[spring-0002] L=200: slow_ops=" + slow + " fast_ops=" + fast +
" ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
{
total++;
long slow = slowListenerDedup(500);
long fast = fastListenerDedup(500);
boolean ok = slow > fast * 50;
System.out.println("[spring-0002] L=500: slow_ops=" + slow + " fast_ops=" + fast +
" ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
{
total++;
// Correctness: dedup result must be same size
List<Integer> slowList = new ArrayList<>();
Set<Integer> fastSet = new LinkedHashSet<>();
// Add 100 items including duplicates
for (int i = 0; i < 100; i++) {
int val = i % 80;
if (!slowList.contains(val)) slowList.add(val);
fastSet.add(val);
}
boolean ok = slowList.size() == fastSet.size();
System.out.println("[spring-0002] dedup correctness: slow=" + slowList.size() +
" fast=" + fastSet.size() + " " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
System.out.println("\n" + pass + "/" + total + " PASS");
if (pass != total) {
System.exit(1);
}
}
}