java-topology/defects/quarkus/unit/QuarkusTest.java

236 lines
9.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Unit test for Quarkus CWE-407 defects:
* quarkus-0001: BeanInfo.getBoundInterceptors — bound.contains (ArrayList) in nested loops
* quarkus-0002: ComponentsProviderGenerator.isDependency — dependants.contains (ArrayList) in loop
*
* No JUnit. No external deps. Compile and run:
* javac -d . *.java && java -ea unit.QuarkusTest
*/
public class QuarkusTest {
// ---- quarkus-0001 simulation ----
// Simulates getBoundInterceptors(): nested loops over lifecycle + intercepted methods,
// deduplicating into 'bound' list using ArrayList.contains
static long slowGetBoundInterceptors(int methodCount, int interceptorsPerMethod) {
long ops = 0;
List<Integer> bound = new ArrayList<>();
// Loop 1: lifecycleInterceptors.values()
for (int m = 0; m < methodCount / 2; m++) {
for (int i = 0; i < interceptorsPerMethod; i++) {
int interceptorId = i; // interceptors reused across methods (dedup needed)
ops += bound.size() + 1; // cost of ArrayList.contains scan
if (!bound.contains(interceptorId)) {
bound.add(interceptorId);
}
}
}
// Loop 2: interceptedMethods.values()
for (int m = methodCount / 2; m < methodCount; m++) {
for (int i = 0; i < interceptorsPerMethod; i++) {
int interceptorId = i;
ops += bound.size() + 1;
if (!bound.contains(interceptorId)) {
bound.add(interceptorId);
}
}
}
return ops;
}
static long fastGetBoundInterceptors(int methodCount, int interceptorsPerMethod) {
long ops = 0;
Set<Integer> boundSet = new LinkedHashSet<>();
for (int m = 0; m < methodCount / 2; m++) {
for (int i = 0; i < interceptorsPerMethod; i++) {
int interceptorId = i;
ops += 1; // O(1) HashSet.contains
boundSet.add(interceptorId);
}
}
for (int m = methodCount / 2; m < methodCount; m++) {
for (int i = 0; i < interceptorsPerMethod; i++) {
int interceptorId = i;
ops += 1;
boundSet.add(interceptorId);
}
}
// Convert to sorted List at end (one-time O(I log I))
List<Integer> bound = new ArrayList<>(boundSet);
return ops;
}
// ---- quarkus-0002 simulation ----
// Simulates isDependency called O(B) times, each iterating map values (O(B)) and
// calling dependants.contains (ArrayList, O(D)).
static long slowIsDependency(int beanCount, int dependantsPerBean) {
long ops = 0;
// dependencyMap: bean → list of dependants
Map<Integer, List<Integer>> dependencyMap = new TreeMap<>();
for (int b = 0; b < beanCount; b++) {
List<Integer> dependants = new ArrayList<>();
for (int d = 0; d < dependantsPerBean; d++) {
dependants.add((b + d + 1) % beanCount);
}
dependencyMap.put(b, dependants);
}
// isDependency called for each bean (O(B) calls total)
for (int queryBean = 0; queryBean < beanCount; queryBean++) {
for (List<Integer> dependants : dependencyMap.values()) { // O(B) map values
ops += dependants.size(); // ArrayList.contains scan cost
if (dependants.contains(queryBean)) {
break;
}
}
}
return ops;
}
static long fastIsDependency(int beanCount, int dependantsPerBean) {
long ops = 0;
Map<Integer, List<Integer>> dependencyMap = new TreeMap<>();
for (int b = 0; b < beanCount; b++) {
List<Integer> dependants = new ArrayList<>();
for (int d = 0; d < dependantsPerBean; d++) {
dependants.add((b + d + 1) % beanCount);
}
dependencyMap.put(b, dependants);
}
// Build inverted index once: O(B×D)
Set<Integer> allDependants = new HashSet<>();
for (List<Integer> dependants : dependencyMap.values()) {
allDependants.addAll(dependants);
}
// isDependency is now O(1) per call
for (int queryBean = 0; queryBean < beanCount; queryBean++) {
ops += 1; // O(1) HashSet.contains
allDependants.contains(queryBean);
}
return ops;
}
public static void main(String[] args) {
int pass = 0;
int total = 0;
// --- quarkus-0001 tests ---
{
total++;
long slow = slowGetBoundInterceptors(20, 8);
long fast = fastGetBoundInterceptors(20, 8);
boolean ok = slow > fast * 3;
System.out.println("[quarkus-0001] M=20 I=8: slow_ops=" + slow + " fast_ops=" + fast +
" ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
{
total++;
long slow = slowGetBoundInterceptors(50, 15);
long fast = fastGetBoundInterceptors(50, 15);
boolean ok = slow > fast * 5;
System.out.println("[quarkus-0001] M=50 I=15: slow_ops=" + slow + " fast_ops=" + fast +
" ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
{
total++;
// Correctness: both paths must produce same unique interceptor count
Set<Integer> slowBound = new LinkedHashSet<>();
Set<Integer> fastBound = new LinkedHashSet<>();
int methods = 10, interceptors = 5;
// slow: uses ArrayList dedup but we track the same set for checking
List<Integer> slowList = new ArrayList<>();
for (int m = 0; m < methods; m++) {
for (int i = 0; i < interceptors; i++) {
if (!slowList.contains(i)) slowList.add(i);
}
}
Set<Integer> fastSet = new LinkedHashSet<>();
for (int m = 0; m < methods; m++) {
for (int i = 0; i < interceptors; i++) {
fastSet.add(i);
}
}
boolean ok = slowList.size() == fastSet.size();
System.out.println("[quarkus-0001] correctness: slow=" + slowList.size() +
" fast=" + fastSet.size() + " " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
// --- quarkus-0002 tests ---
{
total++;
long slow = slowIsDependency(100, 5);
long fast = fastIsDependency(100, 5);
boolean ok = slow > fast * 20;
System.out.println("[quarkus-0002] B=100 D=5: slow_ops=" + slow + " fast_ops=" + fast +
" ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
{
total++;
long slow = slowIsDependency(300, 10);
long fast = fastIsDependency(300, 10);
boolean ok = slow > fast * 100;
System.out.println("[quarkus-0002] B=300 D=10: slow_ops=" + slow + " fast_ops=" + fast +
" ratio=" + (slow / Math.max(fast, 1)) + "x " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
{
total++;
// Correctness: isDependency returns same true/false for same query
Map<Integer, List<Integer>> dmap = new HashMap<>();
dmap.put(0, new ArrayList<>(List.of(1, 2, 3)));
dmap.put(1, new ArrayList<>(List.of(4, 5)));
dmap.put(2, new ArrayList<>(List.of(6)));
// slow: iterate all lists, call contains
boolean slowResult3 = false;
boolean slowResult7 = false;
for (List<Integer> deps : dmap.values()) {
if (deps.contains(3)) { slowResult3 = true; break; }
}
for (List<Integer> deps : dmap.values()) {
if (deps.contains(7)) { slowResult7 = true; break; }
}
// fast: precompute set
Set<Integer> allDeps = new HashSet<>();
for (List<Integer> deps : dmap.values()) allDeps.addAll(deps);
boolean fastResult3 = allDeps.contains(3);
boolean fastResult7 = allDeps.contains(7);
boolean ok = slowResult3 == fastResult3 && slowResult7 == fastResult7
&& slowResult3 == true && slowResult7 == false;
System.out.println("[quarkus-0002] isDependency correctness: bean3=" + fastResult3 +
" bean7=" + fastResult7 + " " + (ok ? "PASS" : "FAIL"));
if (ok) pass++;
}
System.out.println("\n" + pass + "/" + total + " PASS");
if (pass != total) {
System.exit(1);
}
}
}