Add 88 new defect entries to HIGH and MEDIUM tables:
HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002
MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
ovs-0001, onos-0003, odl-0002, jetty-0001
PDF: 976K
370 lines
17 KiB
Java
370 lines
17 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.LinkedHashSet;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* Unit tests for CWE-407 defects in Spring Framework.
|
||
*
|
||
* spring-0003 (HIGH): AbstractApplicationEventMulticaster.retrieveApplicationListeners()
|
||
* builds allListeners as ArrayList. Inside the O(L) listenerBeans loop it calls
|
||
* allListeners.contains() twice (lines 279, 285). Total O(L × (P+L)) = O(n²).
|
||
* Hit on every event dispatch cache-miss.
|
||
*
|
||
* spring-0004 (MEDIUM): DefaultListenerRetriever.getApplicationListeners() has the
|
||
* same pattern — ArrayList allListeners, allListeners.contains(listener) in loop
|
||
* over applicationListenerBeans (line 512).
|
||
*
|
||
* Run: java -ea -cp . unit.SpringEventMulticasterTest
|
||
*/
|
||
public class SpringEventMulticasterTest {
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Listener model — identity-equal objects (like ApplicationListener instances)
|
||
// -----------------------------------------------------------------------
|
||
|
||
static final class Listener {
|
||
final String name;
|
||
final boolean isProxy;
|
||
final Listener target; // non-null if this is a proxy
|
||
|
||
Listener(String name) { this.name = name; this.isProxy = false; this.target = null; }
|
||
Listener(String name, Listener target) { this.name = name; this.isProxy = true; this.target = target; }
|
||
|
||
@Override public String toString() { return (isProxy ? "Proxy(" : "Listener(") + name + ")"; }
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// spring-0003 model: retrieveApplicationListeners() — ArrayList vs LinkedHashSet
|
||
// -----------------------------------------------------------------------
|
||
|
||
static final class DefectiveRetriever {
|
||
long containsProbes = 0;
|
||
|
||
/** Models the defective retrieveApplicationListeners() inner loop. */
|
||
List<Listener> retrieve(List<Listener> programmatic, List<Listener[]> beanListeners) {
|
||
// allListeners starts with programmatic listeners
|
||
List<Listener> allListeners = new ArrayList<>(programmatic);
|
||
|
||
for (Listener[] pair : beanListeners) {
|
||
// pair[0] = proxy, pair[1] = unwrapped target (or same if not proxy)
|
||
Listener listener = pair[0];
|
||
Listener unwrapped = pair[1];
|
||
|
||
if (listener != unwrapped) {
|
||
// Line 279: allListeners.contains(unwrappedListener)
|
||
containsProbes += allListeners.size(); // O(n) scan
|
||
if (allListeners.contains(unwrapped)) {
|
||
allListeners.remove(unwrapped);
|
||
allListeners.add(listener);
|
||
continue;
|
||
}
|
||
}
|
||
// Line 285: !allListeners.contains(listener)
|
||
containsProbes += allListeners.size(); // O(n) scan
|
||
if (!allListeners.contains(listener)) {
|
||
allListeners.add(listener);
|
||
}
|
||
}
|
||
return allListeners;
|
||
}
|
||
}
|
||
|
||
static final class FixedRetriever {
|
||
long containsProbes = 0;
|
||
|
||
/** Models the fixed retrieveApplicationListeners() using LinkedHashSet. */
|
||
List<Listener> retrieve(List<Listener> programmatic, List<Listener[]> beanListeners) {
|
||
LinkedHashSet<Listener> allListenerSet = new LinkedHashSet<>(programmatic);
|
||
|
||
for (Listener[] pair : beanListeners) {
|
||
Listener listener = pair[0];
|
||
Listener unwrapped = pair[1];
|
||
|
||
if (listener != unwrapped) {
|
||
containsProbes += 1; // O(1) LinkedHashSet.remove()
|
||
if (allListenerSet.remove(unwrapped)) {
|
||
allListenerSet.add(listener);
|
||
continue;
|
||
}
|
||
}
|
||
containsProbes += 1; // O(1) LinkedHashSet.contains()
|
||
if (!allListenerSet.contains(listener)) {
|
||
allListenerSet.add(listener);
|
||
}
|
||
}
|
||
return new ArrayList<>(allListenerSet);
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// spring-0004 model: DefaultListenerRetriever.getApplicationListeners()
|
||
// -----------------------------------------------------------------------
|
||
|
||
static final class DefectiveDefaultRetriever {
|
||
long containsProbes = 0;
|
||
|
||
List<Listener> getListeners(List<Listener> programmatic, List<Listener> beanListeners) {
|
||
List<Listener> allListeners = new ArrayList<>(programmatic);
|
||
for (Listener l : beanListeners) {
|
||
containsProbes += allListeners.size(); // O(n) scan — line 512
|
||
if (!allListeners.contains(l)) {
|
||
allListeners.add(l);
|
||
}
|
||
}
|
||
return allListeners;
|
||
}
|
||
}
|
||
|
||
static final class FixedDefaultRetriever {
|
||
long containsProbes = 0;
|
||
|
||
List<Listener> getListeners(List<Listener> programmatic, List<Listener> beanListeners) {
|
||
LinkedHashSet<Listener> allListenerSet = new LinkedHashSet<>(programmatic);
|
||
for (Listener l : beanListeners) {
|
||
containsProbes += 1; // O(1) LinkedHashSet.add()
|
||
allListenerSet.add(l);
|
||
}
|
||
return new ArrayList<>(allListenerSet);
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Test 1 — spring-0003 correctness: proxy replacement and dedup
|
||
// -----------------------------------------------------------------------
|
||
static void test1_spring0003_correctness() {
|
||
Listener a = new Listener("A");
|
||
Listener b = new Listener("B");
|
||
Listener bProxy = new Listener("B-proxy", b); // proxy wrapping b
|
||
|
||
List<Listener> programmatic = List.of(a, b);
|
||
// beanListeners: a proxy for b (should replace b in the list)
|
||
List<Listener[]> beanListeners = new ArrayList<>();
|
||
beanListeners.add(new Listener[]{bProxy, b});
|
||
|
||
DefectiveRetriever def = new DefectiveRetriever();
|
||
FixedRetriever fix = new FixedRetriever();
|
||
|
||
List<Listener> defResult = def.retrieve(programmatic, beanListeners);
|
||
List<Listener> fixResult = fix.retrieve(programmatic, beanListeners);
|
||
|
||
assert defResult.size() == fixResult.size()
|
||
: "spring-0003 correctness: size differs def=" + defResult.size() + " fix=" + fixResult.size();
|
||
assert defResult.equals(fixResult)
|
||
: "spring-0003 correctness: listener lists differ: def=" + defResult + " fix=" + fixResult;
|
||
|
||
// b should be replaced by bProxy
|
||
assert !fixResult.contains(b)
|
||
: "spring-0003 correctness: bare b should be replaced by proxy";
|
||
assert fixResult.contains(bProxy)
|
||
: "spring-0003 correctness: bProxy should be present";
|
||
|
||
System.out.println("PASS test1_spring0003_correctness: proxy replacement correct, size=" + fixResult.size());
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Test 2 — spring-0003 complexity: O(n²) vs O(n) probes
|
||
// -----------------------------------------------------------------------
|
||
static void test2_spring0003_complexity_ratio() {
|
||
int small = 50;
|
||
int large = 500;
|
||
|
||
// Build inputs: P programmatic listeners, L bean listeners (all unique, no proxies)
|
||
List<Listener> smallProg = new ArrayList<>(), largeProg = new ArrayList<>();
|
||
List<Listener[]> smallBean = new ArrayList<>(), largeBean = new ArrayList<>();
|
||
|
||
for (int i = 0; i < small; i++) {
|
||
smallProg.add(new Listener("prog-" + i));
|
||
}
|
||
for (int i = 0; i < small; i++) {
|
||
Listener l = new Listener("bean-" + i);
|
||
smallBean.add(new Listener[]{l, l}); // no proxy
|
||
}
|
||
for (int i = 0; i < large; i++) {
|
||
largeProg.add(new Listener("prog-" + i));
|
||
}
|
||
for (int i = 0; i < large; i++) {
|
||
Listener l = new Listener("bean-" + i);
|
||
largeBean.add(new Listener[]{l, l});
|
||
}
|
||
|
||
DefectiveRetriever defSmall = new DefectiveRetriever();
|
||
DefectiveRetriever defLarge = new DefectiveRetriever();
|
||
FixedRetriever fixSmall = new FixedRetriever();
|
||
FixedRetriever fixLarge = new FixedRetriever();
|
||
|
||
defSmall.retrieve(smallProg, smallBean);
|
||
defLarge.retrieve(largeProg, largeBean);
|
||
fixSmall.retrieve(smallProg, smallBean);
|
||
fixLarge.retrieve(largeProg, largeBean);
|
||
|
||
double defRatio = (double) defLarge.containsProbes / defSmall.containsProbes;
|
||
double fixRatio = (double) fixLarge.containsProbes / fixSmall.containsProbes;
|
||
|
||
System.out.printf(" defective probes: small=%d large=%d ratio=%.1fx%n",
|
||
defSmall.containsProbes, defLarge.containsProbes, defRatio);
|
||
System.out.printf(" fixed probes: small=%d large=%d ratio=%.1fx%n",
|
||
fixSmall.containsProbes, fixLarge.containsProbes, fixRatio);
|
||
|
||
assert defRatio > 50.0
|
||
: "spring-0003 complexity: defective ratio should be >50x, got " + defRatio;
|
||
assert fixRatio < 20.0
|
||
: "spring-0003 complexity: fixed ratio should be <20x, got " + fixRatio;
|
||
|
||
System.out.printf("PASS test2_spring0003_complexity_ratio: defective=%.0fx fixed=%.0fx%n", defRatio, fixRatio);
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Test 3 — spring-0003 absolute counts at N=200
|
||
// -----------------------------------------------------------------------
|
||
static void test3_spring0003_absolute_counts() {
|
||
int P = 100, L = 100; // 100 programmatic + 100 bean listeners, all unique
|
||
List<Listener> prog = new ArrayList<>();
|
||
List<Listener[]> bean = new ArrayList<>();
|
||
for (int i = 0; i < P; i++) prog.add(new Listener("prog-" + i));
|
||
for (int i = 0; i < L; i++) {
|
||
Listener l = new Listener("bean-" + i);
|
||
bean.add(new Listener[]{l, l});
|
||
}
|
||
|
||
DefectiveRetriever def = new DefectiveRetriever();
|
||
FixedRetriever fix = new FixedRetriever();
|
||
def.retrieve(prog, bean);
|
||
fix.retrieve(prog, bean);
|
||
|
||
// Defective: each of L iterations checks list of size (P+0..L-1) twice
|
||
// Lower bound: L * P probes (at minimum the initial P listeners are scanned)
|
||
long expectedDefMin = (long) L * P;
|
||
assert def.containsProbes >= expectedDefMin
|
||
: "spring-0003 counts: defective probes=" + def.containsProbes + " expected>=" + expectedDefMin;
|
||
|
||
// Fixed: exactly L probes (one per bean listener)
|
||
assert fix.containsProbes == L
|
||
: "spring-0003 counts: fixed probes=" + fix.containsProbes + " expected=" + L;
|
||
|
||
long speedup = def.containsProbes / fix.containsProbes;
|
||
System.out.printf("PASS test3_spring0003_absolute_counts: defective=%d fixed=%d speedup=%dx%n",
|
||
def.containsProbes, fix.containsProbes, speedup);
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Test 4 — spring-0004 correctness
|
||
// -----------------------------------------------------------------------
|
||
static void test4_spring0004_correctness() {
|
||
Listener a = new Listener("A"), b = new Listener("B"), c = new Listener("C");
|
||
|
||
List<Listener> programmatic = List.of(a, b);
|
||
List<Listener> beanListeners = List.of(b, c); // b is duplicate
|
||
|
||
DefectiveDefaultRetriever def = new DefectiveDefaultRetriever();
|
||
FixedDefaultRetriever fix = new FixedDefaultRetriever();
|
||
|
||
List<Listener> defResult = def.getListeners(programmatic, beanListeners);
|
||
List<Listener> fixResult = fix.getListeners(programmatic, beanListeners);
|
||
|
||
assert defResult.size() == 3
|
||
: "spring-0004 correctness: defective size=" + defResult.size() + " expected=3";
|
||
assert fixResult.size() == 3
|
||
: "spring-0004 correctness: fixed size=" + fixResult.size() + " expected=3";
|
||
assert defResult.equals(fixResult)
|
||
: "spring-0004 correctness: results differ def=" + defResult + " fix=" + fixResult;
|
||
|
||
System.out.println("PASS test4_spring0004_correctness: size=3, dedup correct");
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Test 5 — spring-0004 complexity ratio
|
||
// -----------------------------------------------------------------------
|
||
static void test5_spring0004_complexity_ratio() {
|
||
int small = 50, large = 500;
|
||
|
||
List<Listener> smallProg = new ArrayList<>(), largeProg = new ArrayList<>();
|
||
List<Listener> smallBean = new ArrayList<>(), largeBean = new ArrayList<>();
|
||
|
||
for (int i = 0; i < small; i++) smallProg.add(new Listener("p" + i));
|
||
for (int i = 0; i < small; i++) smallBean.add(new Listener("b" + i));
|
||
for (int i = 0; i < large; i++) largeProg.add(new Listener("p" + i));
|
||
for (int i = 0; i < large; i++) largeBean.add(new Listener("b" + i));
|
||
|
||
DefectiveDefaultRetriever defSmall = new DefectiveDefaultRetriever();
|
||
DefectiveDefaultRetriever defLarge = new DefectiveDefaultRetriever();
|
||
FixedDefaultRetriever fixSmall = new FixedDefaultRetriever();
|
||
FixedDefaultRetriever fixLarge = new FixedDefaultRetriever();
|
||
|
||
defSmall.getListeners(smallProg, smallBean);
|
||
defLarge.getListeners(largeProg, largeBean);
|
||
fixSmall.getListeners(smallProg, smallBean);
|
||
fixLarge.getListeners(largeProg, largeBean);
|
||
|
||
double defRatio = (double) defLarge.containsProbes / defSmall.containsProbes;
|
||
double fixRatio = (double) fixLarge.containsProbes / fixSmall.containsProbes;
|
||
|
||
System.out.printf(" defective probes: small=%d large=%d ratio=%.1fx%n",
|
||
defSmall.containsProbes, defLarge.containsProbes, defRatio);
|
||
System.out.printf(" fixed probes: small=%d large=%d ratio=%.1fx%n",
|
||
fixSmall.containsProbes, fixLarge.containsProbes, fixRatio);
|
||
|
||
assert defRatio > 50.0
|
||
: "spring-0004 complexity: defective ratio should be >50x, got " + defRatio;
|
||
assert fixRatio < 20.0
|
||
: "spring-0004 complexity: fixed ratio should be <20x, got " + fixRatio;
|
||
|
||
System.out.printf("PASS test5_spring0004_complexity_ratio: defective=%.0fx fixed=%.0fx%n", defRatio, fixRatio);
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Test 6 — spring-0004 absolute counts at N=200
|
||
// -----------------------------------------------------------------------
|
||
static void test6_spring0004_absolute_counts() {
|
||
int P = 100, L = 100;
|
||
List<Listener> prog = new ArrayList<>(), bean = new ArrayList<>();
|
||
for (int i = 0; i < P; i++) prog.add(new Listener("p" + i));
|
||
for (int i = 0; i < L; i++) bean.add(new Listener("b" + i)); // all unique
|
||
|
||
DefectiveDefaultRetriever def = new DefectiveDefaultRetriever();
|
||
FixedDefaultRetriever fix = new FixedDefaultRetriever();
|
||
def.getListeners(prog, bean);
|
||
fix.getListeners(prog, bean);
|
||
|
||
long expectedDefMin = (long) L * P;
|
||
assert def.containsProbes >= expectedDefMin
|
||
: "spring-0004 counts: defective probes=" + def.containsProbes + " expected>=" + expectedDefMin;
|
||
assert fix.containsProbes == L
|
||
: "spring-0004 counts: fixed probes=" + fix.containsProbes + " expected=" + L;
|
||
|
||
System.out.printf("PASS test6_spring0004_absolute_counts: defective=%d fixed=%d speedup=%dx%n",
|
||
def.containsProbes, fix.containsProbes, def.containsProbes / fix.containsProbes);
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// main
|
||
// -----------------------------------------------------------------------
|
||
public static void main(String[] args) {
|
||
System.out.println("=== SpringEventMulticasterTest — CWE-407 spring-0003 / spring-0004 ===");
|
||
int passed = 0, failed = 0;
|
||
|
||
Runnable[] tests = {
|
||
SpringEventMulticasterTest::test1_spring0003_correctness,
|
||
SpringEventMulticasterTest::test2_spring0003_complexity_ratio,
|
||
SpringEventMulticasterTest::test3_spring0003_absolute_counts,
|
||
SpringEventMulticasterTest::test4_spring0004_correctness,
|
||
SpringEventMulticasterTest::test5_spring0004_complexity_ratio,
|
||
SpringEventMulticasterTest::test6_spring0004_absolute_counts,
|
||
};
|
||
|
||
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);
|
||
}
|
||
}
|