219 lines
8.8 KiB
Java
219 lines
8.8 KiB
Java
import java.util.*;
|
||
import java.util.stream.*;
|
||
|
||
/**
|
||
* CWE-407 unit tests for micronaut-core defects.
|
||
*
|
||
* micronaut-core-0001: HttpClientIntroductionAdvice.buildDefaultBinder
|
||
* calls uriCtx.getUriTemplate().getVariableNames().contains(argument.getName())
|
||
* inside a per-argument lambda. getVariableNames() allocates a new List<String>
|
||
* each invocation and List.contains() is O(V). Called once per argument per
|
||
* request → O(A×V) per call.
|
||
* Fix: pre-compute Set<String> from variable names once before building the binder.
|
||
*
|
||
* micronaut-core-0002: DefaultBeanContext.topologicalSort
|
||
* Two defects:
|
||
* (a) unsorted.stream().map(...).map(...).anyMatch(clazz::isAssignableFrom) is
|
||
* O(B) stream reconstruction called for each component of each bean in each
|
||
* outer while-iteration → O(B²×C) overall.
|
||
* Fix: pre-build Set<Class<?>> unsortedBeanTypes and update on removal.
|
||
* (b) sorted.add(0, bean) is O(B) ArrayList prepend → O(B²) total.
|
||
* Fix: use ArrayDeque.addFirst() O(1).
|
||
*/
|
||
public class MicronautCoreTest {
|
||
|
||
// -----------------------------------------------------------------------
|
||
// micronaut-core-0001: URI variable lookup in HTTP client binder
|
||
// -----------------------------------------------------------------------
|
||
|
||
/** Simulates defective per-argument URI variable lookup using List.contains(). */
|
||
static boolean uriVarLookupDefect(List<String> argNames, List<String> uriVars) {
|
||
int hits = 0;
|
||
for (String arg : argNames) {
|
||
// Defect: getVariableNames() re-invoked inside lambda → new List each time
|
||
List<String> freshList = new ArrayList<>(uriVars); // simulate getVariableNames()
|
||
if (freshList.contains(arg)) { // O(V) scan
|
||
hits++;
|
||
}
|
||
}
|
||
return hits > 0;
|
||
}
|
||
|
||
/** Fixed version: pre-compute Set<String> once, O(1) lookup. */
|
||
static boolean uriVarLookupFixed(List<String> argNames, List<String> uriVars) {
|
||
Set<String> varSet = new HashSet<>(uriVars); // computed once, O(V)
|
||
int hits = 0;
|
||
for (String arg : argNames) {
|
||
if (varSet.contains(arg)) { // O(1) per lookup
|
||
hits++;
|
||
}
|
||
}
|
||
return hits > 0;
|
||
}
|
||
|
||
static long timeUriVarLookup(boolean fixed, int argCount, int varCount) {
|
||
List<String> args = IntStream.range(0, argCount).mapToObj(i -> "arg" + i).collect(Collectors.toList());
|
||
List<String> vars = IntStream.range(0, varCount).mapToObj(i -> "var" + i).collect(Collectors.toList());
|
||
// Seed with one match so result is stable
|
||
if (!args.isEmpty() && !vars.isEmpty()) vars.set(0, args.get(0));
|
||
|
||
int REPS = 2000;
|
||
long start = System.nanoTime();
|
||
boolean r = false;
|
||
for (int rep = 0; rep < REPS; rep++) {
|
||
r = fixed ? uriVarLookupFixed(args, vars) : uriVarLookupDefect(args, vars);
|
||
}
|
||
long elapsed = System.nanoTime() - start;
|
||
if (!r) throw new AssertionError("should have matched");
|
||
return elapsed / REPS;
|
||
}
|
||
|
||
static void testUriVarLookup() {
|
||
int N = 100; // 100 args, 100 uri vars — amplifies the O(A*V) cost
|
||
long defectNs = timeUriVarLookup(false, N, N);
|
||
long fixedNs = timeUriVarLookup(true, N, N);
|
||
double ratio = (double) defectNs / fixedNs;
|
||
System.out.printf("micronaut-core-0001 [N=%d]: defect=%dns fixed=%dns ratio=%.1fx%n",
|
||
N, defectNs, fixedNs, ratio);
|
||
if (ratio < 2.0) {
|
||
throw new AssertionError("Expected ratio >= 2.0x, got " + ratio);
|
||
}
|
||
System.out.println("micronaut-core-0001 PASS");
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// micronaut-core-0002: topologicalSort stream scan + ArrayList prepend
|
||
// -----------------------------------------------------------------------
|
||
|
||
/**
|
||
* Simulates the topologicalSort inner stream scan:
|
||
* for each bean, for each required component, scan all remaining unsorted beans.
|
||
* Defect: O(B²×C) — stream rebuilt from List each check.
|
||
*/
|
||
static List<Integer> topoSortDefect(List<Integer[]> beans) {
|
||
// beans[i] = {beanId, requiredBeanId_or_-1_if_none}
|
||
List<Integer> unsorted = new ArrayList<>();
|
||
List<Integer> sorted = new ArrayList<>();
|
||
for (Integer[] b : beans) {
|
||
if (b[1] < 0) sorted.add(0, b[0]);
|
||
else unsorted.add(b[0]);
|
||
}
|
||
Map<Integer, Integer> required = new HashMap<>();
|
||
for (Integer[] b : beans) {
|
||
if (b[1] >= 0) required.put(b[0], b[1]);
|
||
}
|
||
|
||
while (!unsorted.isEmpty()) {
|
||
boolean acyclic = false;
|
||
Iterator<Integer> i = unsorted.iterator();
|
||
while (i.hasNext()) {
|
||
int bean = i.next();
|
||
int req = required.getOrDefault(bean, -1);
|
||
if (req < 0) {
|
||
i.remove();
|
||
sorted.add(0, bean); // O(B) prepend
|
||
acyclic = true;
|
||
} else {
|
||
// Defect: O(B) stream scan for each bean each iteration
|
||
boolean stillUnsorted = unsorted.stream().anyMatch(u -> u == req);
|
||
if (!stillUnsorted) {
|
||
i.remove();
|
||
sorted.add(0, bean); // O(B) prepend
|
||
acyclic = true;
|
||
}
|
||
}
|
||
}
|
||
if (!acyclic && !unsorted.isEmpty()) {
|
||
sorted.add(0, unsorted.remove(0));
|
||
}
|
||
}
|
||
return sorted;
|
||
}
|
||
|
||
/**
|
||
* Fixed version: pre-build Set<Integer> unsortedSet (O(1) lookup),
|
||
* use ArrayDeque for O(1) prepend.
|
||
*/
|
||
static List<Integer> topoSortFixed(List<Integer[]> beans) {
|
||
List<Integer> unsorted = new ArrayList<>();
|
||
ArrayDeque<Integer> sortedDeque = new ArrayDeque<>();
|
||
for (Integer[] b : beans) {
|
||
if (b[1] < 0) sortedDeque.addFirst(b[0]);
|
||
else unsorted.add(b[0]);
|
||
}
|
||
Map<Integer, Integer> required = new HashMap<>();
|
||
for (Integer[] b : beans) {
|
||
if (b[1] >= 0) required.put(b[0], b[1]);
|
||
}
|
||
|
||
// Pre-build set of unsorted bean IDs
|
||
Set<Integer> unsortedSet = new HashSet<>(unsorted);
|
||
|
||
while (!unsorted.isEmpty()) {
|
||
boolean acyclic = false;
|
||
Iterator<Integer> i = unsorted.iterator();
|
||
while (i.hasNext()) {
|
||
int bean = i.next();
|
||
int req = required.getOrDefault(bean, -1);
|
||
if (req < 0 || !unsortedSet.contains(req)) { // O(1)
|
||
i.remove();
|
||
sortedDeque.addFirst(bean); // O(1)
|
||
unsortedSet.remove(bean);
|
||
acyclic = true;
|
||
}
|
||
}
|
||
if (!acyclic && !unsorted.isEmpty()) {
|
||
int removed = unsorted.remove(0);
|
||
sortedDeque.addFirst(removed);
|
||
unsortedSet.remove(removed);
|
||
}
|
||
}
|
||
return new ArrayList<>(sortedDeque);
|
||
}
|
||
|
||
/** Build a chain: bean[i] requires bean[i-1], so they must sort in order. */
|
||
static List<Integer[]> buildBeanChain(int n) {
|
||
List<Integer[]> beans = new ArrayList<>();
|
||
beans.add(new Integer[]{0, -1}); // root, no dependency
|
||
for (int i = 1; i < n; i++) {
|
||
beans.add(new Integer[]{i, i - 1}); // bean i requires bean i-1
|
||
}
|
||
return beans;
|
||
}
|
||
|
||
static long timeTopoSort(boolean fixed, int n) {
|
||
List<Integer[]> beans = buildBeanChain(n);
|
||
int REPS = 100;
|
||
long start = System.nanoTime();
|
||
List<Integer> result = null;
|
||
for (int r = 0; r < REPS; r++) {
|
||
result = fixed ? topoSortFixed(new ArrayList<>(beans)) : topoSortDefect(new ArrayList<>(beans));
|
||
}
|
||
long elapsed = System.nanoTime() - start;
|
||
if (result == null || result.isEmpty()) throw new AssertionError("null result");
|
||
return elapsed / REPS;
|
||
}
|
||
|
||
static void testTopoSort() {
|
||
int N = 400; // chain of 400 beans — O(B²) vs O(B) gap widens
|
||
long defectNs = timeTopoSort(false, N);
|
||
long fixedNs = timeTopoSort(true, N);
|
||
double ratio = (double) defectNs / fixedNs;
|
||
System.out.printf("micronaut-core-0002 [N=%d]: defect=%dns fixed=%dns ratio=%.1fx%n",
|
||
N, defectNs, fixedNs, ratio);
|
||
if (ratio < 2.0) {
|
||
throw new AssertionError("Expected ratio >= 2.0x, got " + ratio);
|
||
}
|
||
System.out.println("micronaut-core-0002 PASS");
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// main
|
||
// -----------------------------------------------------------------------
|
||
|
||
public static void main(String[] args) {
|
||
testUriVarLookup();
|
||
testTopoSort();
|
||
System.out.println("ALL PASS");
|
||
}
|
||
}
|