undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,355 @@
package unit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.LinkedHashSet;
/**
* Unit tests for CWE-407 defects in Spring Framework.
*
* spring-0001 (HIGH): BeanFactoryUtils.mergeNamesWithParent() used ArrayList
* for dedup — merged.contains(beanName) is O(|result|) per parentResult
* element, giving O(|result| × |parentResult|) = O(B²) overall.
*
* spring-0002 (LOW-MEDIUM): ImportStack extends ArrayDeque<ConfigurationClass>;
* ArrayDeque.contains() is O(n), called in processMemberClasses() and
* isChainedImportOnStack() once per candidate.
*
* Run: java -ea -cp . unit.SpringBeanFactoryTest
*/
public class SpringBeanFactoryTest {
// -----------------------------------------------------------------------
// spring-0001 models
// -----------------------------------------------------------------------
/** Defective: ArrayList-based merge with O(n) contains per element. */
static MergeResult mergeDefective(String[] result, String[] parentResult) {
long comparisons = 0;
ArrayList<String> merged = new ArrayList<>(result.length + parentResult.length);
merged.addAll(Arrays.asList(result));
for (String beanName : parentResult) {
// Each contains() scan walks the entire merged list — O(|merged|)
comparisons += merged.size();
if (!merged.contains(beanName)) {
merged.add(beanName);
}
}
return new MergeResult(merged.toArray(new String[0]), comparisons);
}
/** Fixed: LinkedHashSet-based merge with O(1) add (set dedup). */
static MergeResult mergeFixed(String[] result, String[] parentResult) {
long comparisons = 0;
LinkedHashSet<String> merged = new LinkedHashSet<>(Arrays.asList(result));
for (String beanName : parentResult) {
comparisons += 1; // O(1) hash lookup per element
merged.add(beanName); // Set.add() is idempotent; no contains() guard
}
return new MergeResult(merged.toArray(new String[0]), comparisons);
}
static class MergeResult {
final String[] names;
final long comparisons;
MergeResult(String[] names, long comparisons) {
this.names = names;
this.comparisons = comparisons;
}
}
// -----------------------------------------------------------------------
// spring-0002 models
// -----------------------------------------------------------------------
/** Defective: plain ArrayDeque — contains() is O(n). */
static class DefectiveImportStack extends ArrayDeque<String> {
long containsCalls = 0;
long totalProbes = 0;
@Override
public boolean contains(Object o) {
containsCalls++;
totalProbes += size(); // ArrayDeque scans all elements
return super.contains(o);
}
}
/** Fixed: ArrayDeque + parallel HashSet — contains() is O(1). */
static class FixedImportStack extends ArrayDeque<String> {
private final HashSet<String> members = new HashSet<>();
long containsCalls = 0;
long totalProbes = 0;
@Override
public void push(String item) {
super.push(item);
members.add(item);
}
@Override
public String pop() {
String item = super.pop();
members.remove(item);
return item;
}
@Override
public void clear() {
super.clear();
members.clear();
}
@Override
public boolean contains(Object o) {
containsCalls++;
totalProbes += 1; // O(1) hash lookup
return members.contains(o);
}
}
// -----------------------------------------------------------------------
// Test 1 — spring-0001: correctness (output must match)
// -----------------------------------------------------------------------
static void test1_spring0001_correctness() {
String[] result = {"beanA", "beanB", "beanC"};
String[] parentResult = {"beanB", "beanD", "beanE", "beanC"};
MergeResult defective = mergeDefective(result, parentResult);
MergeResult fixed = mergeFixed(result, parentResult);
assert Arrays.equals(defective.names, fixed.names)
: "spring-0001 correctness: output mismatch — defective=" +
Arrays.toString(defective.names) + " fixed=" + Arrays.toString(fixed.names);
// Expected: beanA, beanB, beanC, beanD, beanE (result first, then new-only from parent)
String[] expected = {"beanA", "beanB", "beanC", "beanD", "beanE"};
assert Arrays.equals(fixed.names, expected)
: "spring-0001 correctness: wrong output — got " + Arrays.toString(fixed.names);
System.out.println("PASS test1_spring0001_correctness: output=" + Arrays.toString(fixed.names));
}
// -----------------------------------------------------------------------
// Test 2 — spring-0001: O(B²) vs O(B) ratio proves quadratic growth
// -----------------------------------------------------------------------
static void test2_spring0001_complexity_ratio() {
// Small scale: B=50 total beans, all in result, 50 in parentResult (all dupes)
int small = 50;
String[] smallResult = new String[small];
String[] smallParent = new String[small];
for (int i = 0; i < small; i++) {
smallResult[i] = "bean-" + i;
smallParent[i] = "bean-" + i; // all duplicates → worst case for contains()
}
// Large scale: B=500
int large = 500;
String[] largeResult = new String[large];
String[] largeParent = new String[large];
for (int i = 0; i < large; i++) {
largeResult[i] = "bean-" + i;
largeParent[i] = "bean-" + i;
}
MergeResult defSmall = mergeDefective(smallResult, smallParent);
MergeResult defLarge = mergeDefective(largeResult, largeParent);
MergeResult fixSmall = mergeFixed(smallResult, smallParent);
MergeResult fixLarge = mergeFixed(largeResult, largeParent);
// Defective: comparisons should scale ~quadratically (10x input → ~100x comparisons)
double defRatio = (double) defLarge.comparisons / defSmall.comparisons;
// Fixed: comparisons should scale ~linearly (10x input → ~10x comparisons)
double fixRatio = (double) fixLarge.comparisons / fixSmall.comparisons;
System.out.printf(" defective comparisons: small=%d large=%d ratio=%.1fx%n",
defSmall.comparisons, defLarge.comparisons, defRatio);
System.out.printf(" fixed comparisons: small=%d large=%d ratio=%.1fx%n",
fixSmall.comparisons, fixLarge.comparisons, fixRatio);
// Defective ratio should be ~100 (quadratic), fixed ratio should be ~10 (linear)
assert defRatio > 50.0
: "spring-0001 complexity: defective ratio should be >50x at 10x scale, got " + defRatio;
assert fixRatio < 20.0
: "spring-0001 complexity: fixed ratio should be <20x at 10x scale, got " + fixRatio;
assert defRatio > fixRatio * 3
: "spring-0001 complexity: defective should grow much faster than fixed, ratios: def=" +
defRatio + " fix=" + fixRatio;
System.out.printf("PASS test2_spring0001_complexity_ratio: defective=%.0fx fixed=%.0fx%n",
defRatio, fixRatio);
}
// -----------------------------------------------------------------------
// Test 3 — spring-0001: absolute comparison counts prove O(B²)
// -----------------------------------------------------------------------
static void test3_spring0001_absolute_counts() {
// With B beans all in result and all duplicated in parentResult:
// defective does sum_{k=B}^{2B-1} k ≈ (3B²/2) comparisons
// fixed does exactly B comparisons (one hash probe per parentResult element)
int B = 200;
String[] result = new String[B];
String[] parent = new String[B];
for (int i = 0; i < B; i++) {
result[i] = "bean-" + i;
parent[i] = "bean-" + i;
}
MergeResult def = mergeDefective(result, parent);
MergeResult fix = mergeFixed(result, parent);
// Defective: each of B parentResult elements triggers a full scan of merged
// (which grows from B to B + new additions). Worst case (all dupes): B*B scans.
long expectedDefMin = (long) B * B; // lower bound: B elements × B scans each
assert def.comparisons >= expectedDefMin
: "spring-0001 counts: defective should do >=" + expectedDefMin +
" comparisons, got " + def.comparisons;
// Fixed: exactly B hash probes (one per parentResult element)
assert fix.comparisons == B
: "spring-0001 counts: fixed should do exactly " + B + " comparisons, got " + fix.comparisons;
long speedup = def.comparisons / fix.comparisons;
System.out.printf("PASS test3_spring0001_absolute_counts: defective=%d fixed=%d speedup=%dx%n",
def.comparisons, fix.comparisons, speedup);
}
// -----------------------------------------------------------------------
// Test 4 — spring-0002: ImportStack contains() complexity
// -----------------------------------------------------------------------
static void test4_spring0002_importstack_complexity() {
int N = 100; // N config classes pushed onto stack
DefectiveImportStack defStack = new DefectiveImportStack();
FixedImportStack fixStack = new FixedImportStack();
// Push N items, then call contains() N times for items at various positions
for (int i = 0; i < N; i++) {
defStack.push("config-" + i);
fixStack.push("config-" + i);
}
// Query contains for each item — worst case, item is at the tail (oldest push)
for (int i = 0; i < N; i++) {
boolean defFound = defStack.contains("config-" + i);
boolean fixFound = fixStack.contains("config-" + i);
assert defFound == fixFound
: "spring-0002 correctness: mismatch at i=" + i +
" defective=" + defFound + " fixed=" + fixFound;
}
// Query for absent items (false lookups — also O(n) in defective)
for (int i = N; i < 2 * N; i++) {
boolean defFound = defStack.contains("config-" + i);
boolean fixFound = fixStack.contains("config-" + i);
assert !defFound : "spring-0002: defective found absent item " + i;
assert !fixFound : "spring-0002: fixed found absent item " + i;
}
System.out.printf(" defective: calls=%d totalProbes=%d avg=%.1f per call%n",
defStack.containsCalls, defStack.totalProbes,
(double) defStack.totalProbes / defStack.containsCalls);
System.out.printf(" fixed: calls=%d totalProbes=%d avg=%.1f per call%n",
fixStack.containsCalls, fixStack.totalProbes,
(double) fixStack.totalProbes / fixStack.containsCalls);
// Defective average probes per call should be O(N); fixed should be O(1)
double defAvg = (double) defStack.totalProbes / defStack.containsCalls;
double fixAvg = (double) fixStack.totalProbes / fixStack.containsCalls;
assert defAvg > N / 2.0
: "spring-0002: defective avg probes should be >N/2=" + (N/2) + ", got " + defAvg;
assert fixAvg == 1.0
: "spring-0002: fixed avg probes should be exactly 1.0, got " + fixAvg;
assert defStack.totalProbes > fixStack.totalProbes * (N / 4)
: "spring-0002: defective probes should be >> fixed probes";
System.out.printf("PASS test4_spring0002_importstack_complexity: " +
"defective_avg=%.0f fixed_avg=%.0f speedup=%.0fx%n",
defAvg, fixAvg, defAvg / fixAvg);
}
// -----------------------------------------------------------------------
// Test 5 — spring-0001: empty parentResult short-circuit (edge case)
// -----------------------------------------------------------------------
static void test5_spring0001_empty_parent_shortcircuit() {
String[] result = {"beanA", "beanB"};
String[] parentResult = {};
// Both should return result unchanged (zero comparisons)
MergeResult def = mergeDefective(result, parentResult);
MergeResult fix = mergeFixed(result, parentResult);
// When parentResult is empty the loop body never executes
assert def.comparisons == 0 && fix.comparisons == 0
: "spring-0001 edge: empty parent should produce zero comparisons";
assert Arrays.equals(def.names, result) && Arrays.equals(fix.names, result)
: "spring-0001 edge: empty parent should return result unchanged";
System.out.println("PASS test5_spring0001_empty_parent_shortcircuit");
}
// -----------------------------------------------------------------------
// Test 6 — spring-0002: pop() removes from HashSet (stack discipline)
// -----------------------------------------------------------------------
static void test6_spring0002_push_pop_discipline() {
FixedImportStack stack = new FixedImportStack();
stack.push("A");
stack.push("B");
stack.push("C");
assert stack.contains("A") : "spring-0002 discipline: A should be present";
assert stack.contains("B") : "spring-0002 discipline: B should be present";
assert stack.contains("C") : "spring-0002 discipline: C should be present";
String popped = stack.pop();
assert popped.equals("C") : "spring-0002 discipline: LIFO — expected C, got " + popped;
assert !stack.contains("C") : "spring-0002 discipline: C should be absent after pop";
assert stack.contains("A") : "spring-0002 discipline: A still present after C pop";
assert stack.contains("B") : "spring-0002 discipline: B still present after C pop";
stack.clear();
assert !stack.contains("A") : "spring-0002 discipline: A absent after clear";
assert !stack.contains("B") : "spring-0002 discipline: B absent after clear";
assert stack.isEmpty() : "spring-0002 discipline: stack should be empty after clear";
System.out.println("PASS test6_spring0002_push_pop_discipline");
}
// -----------------------------------------------------------------------
// main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== SpringBeanFactoryTest — CWE-407 spring-0001 / spring-0002 ===");
int passed = 0;
int failed = 0;
Runnable[] tests = {
SpringBeanFactoryTest::test1_spring0001_correctness,
SpringBeanFactoryTest::test2_spring0001_complexity_ratio,
SpringBeanFactoryTest::test3_spring0001_absolute_counts,
SpringBeanFactoryTest::test4_spring0002_importstack_complexity,
SpringBeanFactoryTest::test5_spring0001_empty_parent_shortcircuit,
SpringBeanFactoryTest::test6_spring0002_push_pop_discipline,
};
for (Runnable test : tests) {
try {
test.run();
passed++;
} catch (AssertionError e) {
System.out.println("FAIL: " + e.getMessage());
failed++;
}
}
System.out.println("---");
System.out.println("Results: " + passed + " passed, " + failed + " failed");
if (failed > 0) {
System.exit(1);
}
}
}