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:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
355
defects/spring/unit/SpringBeanFactoryTest.java
Normal file
355
defects/spring/unit/SpringBeanFactoryTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue