java-topology/defects/groovy/unit/GroovyNamedParamsTest.java

99 lines
3.4 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.List;
import java.util.Map;
import java.util.Set;
/**
* groovy-0001 — StaticTypeCheckingVisitor: collectedNames ArrayList O(E×C)
*
* Demonstrates CWE-407: checkNamedParamsAnnotation uses ArrayList.contains()
* inside a loop over entries, scanning the list linearly on every iteration.
*
* Models StaticTypeCheckingVisitor.checkNamedParamsAnnotation():
* slow(): ArrayList<String> + .contains() inside loop over entries — O(E×C)
* fast(): HashSet<String> + .contains() inside loop over entries — O(E+C)
*
* Ratio must be >= 5x at N=500.
*/
public class GroovyNamedParamsTest {
/**
* Defective path: collectedNames is ArrayList, contains() is O(C) per call.
* Outer loop: E entries (supplied arguments).
* Inner: collectedNames.contains(name) scans all C collected names.
* Total: O(E × C).
*/
static long slow(List<String> collectedNames, List<String> entryKeys) {
long ops = 0;
for (String name : entryKeys) {
// Simulate ArrayList.contains() — linear scan
boolean found = false;
for (int i = 0; i < collectedNames.size(); i++) {
ops++;
if (collectedNames.get(i).equals(name)) {
found = true;
break;
}
}
// If not found, would addStaticTypeError — we just count ops
}
return ops;
}
/**
* Fixed path: collectedNames is HashSet, contains() is O(1) per call.
* Outer loop: E entries. Inner: O(1) hash lookup.
* Total: O(E + C).
*/
static long fast(Set<String> collectedNamesSet, List<String> entryKeys) {
long ops = 0;
for (String name : entryKeys) {
ops++; // one hash probe per entry
collectedNamesSet.contains(name);
}
return ops;
}
public static void main(String[] args) {
int[] sizes = {100, 200, 500, 1000};
int pass = 0, total = 0;
boolean allPassed = true;
for (int N : sizes) {
// Build C collected param names (annotation-declared params)
List<String> collectedList = new ArrayList<>();
Set<String> collectedSet = new HashSet<>();
for (int i = 0; i < N; i++) {
String name = "param" + i;
collectedList.add(name);
collectedSet.add(name);
}
// Build E entry keys (arguments supplied at call site)
// Use names that are NOT in collectedNames to force full scan in slow path
List<String> entries = new ArrayList<>();
for (int i = 0; i < N; i++) {
entries.add("arg" + i); // no match — slow path scans entire list
}
long slowOps = slow(collectedList, entries);
long fastOps = fast(collectedSet, entries);
double ratio = (double) slowOps / fastOps;
boolean pass1 = ratio >= 5.0;
total++;
if (pass1) pass++;
else allPassed = false;
System.out.printf("N=%4d slow=%8d fast=%6d ratio=%7.1fx %s%n",
N, slowOps, fastOps, ratio, pass1 ? "PASS" : "FAIL");
}
System.out.printf("%d/%d PASS%n", pass, total);
if (!allPassed) System.exit(1);
}
}