package unit; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * groovy-0002 — Verifier: Arrays.asList(params).contains(p) O(V×P) per generated variant * * Demonstrates CWE-407: Verifier.addDefaultParameterMethods and * addDefaultParameterConstructors call Arrays.asList(params).contains(p) inside * visitVariableExpression, allocating a new List and scanning linearly on every * variable expression visit. * * Models the inner visitor call pattern: * slow(): Arrays.asList(params).contains(p) — allocates list + O(P) per call * fast(): pre-built HashSet.contains(p) — O(1) per call * * Ratio must be >= 5x at N=500. */ public class GroovyVerifierDefaultParamsTest { /** * Defective path: simulate the visitor visiting V variable expressions, * each time calling Arrays.asList(params).contains(p). * * params: array of P "kept" parameters for this generated variant * allParams: array of P+D parameters for the original method * varExprs: V parameter references seen in the body * * Returns total element comparisons. */ static long slow(String[] params, String[] allParams, List varExprs) { long ops = 0; for (String p : varExprs) { // Arrays.asList(params).contains(p) — O(P) scan, new List allocation boolean inParams = false; for (String x : params) { ops++; if (x.equals(p)) { inParams = true; break; } } if (!inParams) { // Arrays.asList(allParams).contains(p) — second O(P) scan for (String x : allParams) { ops++; if (x.equals(p)) break; } } } return ops; } /** * Fixed path: pre-build HashSet once before the visitor, then O(1) per call. * * Returns total element comparisons (each hash probe = 1 op). */ static long fast(Set paramsSet, Set allParamsSet, List varExprs) { long ops = 0; for (String p : varExprs) { ops++; // one hash probe boolean inParams = paramsSet.contains(p); if (!inParams) { ops++; // second hash probe allParamsSet.contains(p); } } 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) { // P = N parameters in the kept array (generated variant) // V = N variable expressions in the body String[] params = new String[N]; String[] allParams = new String[N + 10]; // original has 10 extra defaulted params for (int i = 0; i < N; i++) { params[i] = "param" + i; allParams[i] = "param" + i; } for (int i = 0; i < 10; i++) { allParams[N + i] = "default" + i; } // Variable expressions: mix of params that ARE in kept set and ones that aren't // Force worst case: all var-exprs reference defaulted params (not in params[]) List varExprs = new ArrayList<>(); for (int i = 0; i < N; i++) { varExprs.add("default" + (i % 10)); } Set paramsSet = new HashSet<>(Arrays.asList(params)); Set allParamsSet = new HashSet<>(Arrays.asList(allParams)); long slowOps = slow(params, allParams, varExprs); long fastOps = fast(paramsSet, allParamsSet, varExprs); 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); } }