package unit; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * gradle-0001 — OptionReader: CollectionUtils.toList(optionNames).contains() rebuilt per method * * Demonstrates CWE-407: new ArrayList allocated + linear scan on every inner-loop iteration. * * Models OptionReader.getOptionValueMethodForOption(): * slow(): for each (optionElement, method) pair, convert String[] to ArrayList and scan linearly * fast(): use Arrays.asList wrapped in HashSet for O(1) membership * * Asserts slow() performs strictly more comparisons than fast() by at least Nx. */ public class GradleOptionReaderTest { /** * Simulates the defective path: * CollectionUtils.toList(optionNames).contains(targetName) * — allocates new ArrayList, scans linearly each call. * * @param methodOptionNames list of option-name arrays, one per @OptionValues method * @param optionElements names being searched (one per OptionElement) * @return total element comparisons performed */ static long slow(List methodOptionNames, List optionElements) { long ops = 0; for (String targetName : optionElements) { for (String[] names : methodOptionNames) { // Defective: allocate list and scan linearly on every call List nameList = new ArrayList<>(Arrays.asList(names)); for (String n : nameList) { ops++; if (n.equals(targetName)) break; } } } return ops; } /** * Simulates the fixed path: * new HashSet<>(Arrays.asList(optionNames)).contains(targetName) * — O(1) per lookup. * * @param methodOptionNames list of option-name arrays, one per @OptionValues method * @param optionElements names being searched * @return total element comparisons performed (1 per HashSet lookup) */ static long fast(List methodOptionNames, List optionElements) { long ops = 0; for (String targetName : optionElements) { for (String[] names : methodOptionNames) { // Fix: HashSet for O(1) contains Set nameSet = new HashSet<>(Arrays.asList(names)); ops++; // O(1) hash lookup nameSet.contains(targetName); } } return ops; } public static void main(String[] args) { // Simulate a realistic Gradle task: // A = 30 option elements (task has many options) // M = 20 @OptionValues methods // N = 8 option names per method annotation int A = 30; int M = 20; int N = 8; // Build method option-name arrays List methodOptionNames = new ArrayList<>(M); for (int m = 0; m < M; m++) { String[] names = new String[N]; for (int n = 0; n < N; n++) { names[n] = "opt-method" + m + "-" + n; } methodOptionNames.add(names); } // Build option elements — mix of hits and misses (worst case = no match, full scan) List optionElements = new ArrayList<>(A); for (int a = 0; a < A; a++) { // Half are misses (not present) — forces slow path to scan all N names optionElements.add(a % 3 == 0 ? "opt-method" + (a % M) + "-0" : "no-match-" + a); } long sOps = slow(methodOptionNames, optionElements); long fOps = fast(methodOptionNames, optionElements); // Expect slow to do at least 3x more comparisons than fast int Nx = 3; boolean pass = sOps > fOps * Nx; System.out.printf("gradle-0001: slow=%d ops fast=%d ops ratio=%.1fx %s%n", sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL"); if (!pass) { System.exit(1); } } }