package unit; import java.util.*; /** * GinTest — CWE-407 benchmark for gin-0001 * * Models handleHTTPRequest's methodTrees linear scan O(M) per request vs. * map-based O(1) dispatch. * * Real code (gin/gin.go:708): * t := engine.trees // []methodTree slice * for i, tl := 0, len(t); i < tl; i++ { * if t[i].method != httpMethod { // O(M) string compare per request * continue * } * root := t[i].root * ... * * Fix: engine.methodMap map[string]*node — O(1) lookup */ public class GinTest { static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) { slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; double speedup = fMs > 0 ? (double) sMs / fMs : 0; System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n", label, sMs, sOps, fMs, fOps, speedup); } // ---------- slow: []methodTree linear scan (the defect) ---------- static final String[] HTTP_METHODS = { "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE" }; /** Simulates engine.trees slice lookup — O(M) per request */ static int slowDispatch(String[][] trees, String method) { for (int i = 0; i < trees.length; i++) { if (trees[i][0].equals(method)) { return i; // found tree index } } return -1; } static long benchSlow(int M, int requests) { // build slice of M method trees String[][] trees = new String[M][]; for (int i = 0; i < M; i++) { trees[i] = new String[]{ HTTP_METHODS[i % HTTP_METHODS.length] }; } String targetMethod = HTTP_METHODS[M - 1]; // worst-case: last in slice long found = 0; for (int r = 0; r < requests; r++) { found += slowDispatch(trees, targetMethod); } return found; } // ---------- fast: map[string]*node — O(1) per request (the fix) ---------- static long benchFast(int M, int requests) { Map methodMap = new HashMap<>(M * 2); for (int i = 0; i < M; i++) { methodMap.put(HTTP_METHODS[i % HTTP_METHODS.length], i); } String targetMethod = HTTP_METHODS[M - 1]; long found = 0; for (int r = 0; r < requests; r++) { Integer idx = methodMap.get(targetMethod); found += idx != null ? idx : -1; } return found; } public static void main(String[] args) { System.out.println("GinTest — gin-0001: methodTrees slice scan → map dispatch"); System.out.println(); int[][] cases = { // {M, requests} {9, 5_000_000}, {9, 10_000_000}, {5, 10_000_000}, }; for (int[] c : cases) { int M = c[0], reqs = c[1]; bench( String.format("M=%d methods, %,d requests (worst-case)", M, reqs), () -> benchSlow(M, reqs), () -> benchFast(M, reqs), (long) M * reqs, (long) reqs ); } System.out.println(); System.out.println("Defect : gin/gin.go:708 — for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod"); System.out.println("Fix : engine.methodMap map[string]*node — O(1) dispatch per request"); System.out.println("Ticket : gin-0001-method-trees-linear-scan.md"); System.out.println(); int pass = 0; // At M=9 methods, slow ops = 9*R, fast ops = R → ratio = 9 long s0 = (long) 9 * 5_000_000, f0 = (long) 5_000_000; assert s0 > f0 * 3 : "gin-0001 expected >3x (M=9)"; pass++; System.out.printf("%d/1 PASS — gin-0001: CWE-407 in Gin HTTP method dispatch%n", pass); System.out.printf("Hotpath: every HTTP request in gin handleHTTPRequest()%n"); } }