99 lines
3.4 KiB
Java
99 lines
3.4 KiB
Java
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);
|
||
}
|
||
}
|