package unit; import java.util.*; /** * SqliteTest — sqlite-0001 * * Proves CWE-407 in SQLite trigger.c: * sqlite-0001: checkColumnOverlap() — sqlite3IdListIndex O(I) scan for each pEList entry; O(E×I) * * Run: javac -d . SqliteTest.java && java -ea unit.SqliteTest */ public class SqliteTest { // ── sqlite-0001: checkColumnOverlap() ──────────────────────────────────── /** * SLOW: mirrors checkColumnOverlap() before fix. * For each expression in pEList, calls sqlite3IdListIndex which is O(I) scan. * Total: O(E × I) where E = pEList.nExpr, I = pIdList.nId. * * @param idListNames watched column names (pIdList) * @param exprNames SET-clause column names (pEList) * @return number of comparison operations performed */ static long checkColumnOverlapSlow(List idListNames, List exprNames) { long ops = 0; for (String exprName : exprNames) { // sqlite3IdListIndex: linear scan of idListNames for (String idName : idListNames) { ops++; if (idName.equalsIgnoreCase(exprName)) break; } } return ops; } /** * FAST: mirrors checkColumnOverlap() after fix. * Builds a case-insensitive HashSet of idListNames once, then O(1) per expression. * Total: O(I + E) — linear. * * @param idListNames watched column names (pIdList) * @param exprNames SET-clause column names (pEList) * @return number of hash-set operations performed */ static long checkColumnOverlapFast(List idListNames, List exprNames) { // Build case-insensitive hash set of pIdList once — O(I) Set idSet = new HashSet<>(); for (String name : idListNames) idSet.add(name.toLowerCase()); long ops = 0; for (String exprName : exprNames) { ops++; // O(1) hash lookup idSet.contains(exprName.toLowerCase()); } return ops; } 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 r = fOps > 0 ? (double)sOps/fOps : 0; System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n", label, sMs, sOps, fMs, fOps, r); } public static void main(String[] args) { System.out.println("=== UNIT sqlite-0001: SQLite CWE-407 ==="); System.out.println(); // Simulate a trigger watching 200 columns (pIdList) and an UPDATE // with 200 SET-clause expressions (pEList). Every expression is present // in the watch list, so every inner scan reaches the end — worst case. final int IDS = 200; // pIdList.nId final int EXPRS = 200; // pEList.nExpr List idList = new ArrayList<>(); List exprList = new ArrayList<>(); for (int i = 0; i < IDS; i++) idList.add("col_" + i); for (int i = 0; i < EXPRS; i++) exprList.add("col_" + i); // all match, worst case long sOps = checkColumnOverlapSlow(idList, exprList); long fOps = checkColumnOverlapFast(idList, exprList); bench("sqlite-0001 checkColumnOverlap list-scan", () -> checkColumnOverlapSlow(idList, exprList), () -> checkColumnOverlapFast(idList, exprList), sOps, fOps); System.out.println(); int pass = 0; assert sOps > fOps * 5 : "sqlite-0001 expected >5x ops ratio"; pass++; assert checkColumnOverlapFast(idList, exprList) == EXPRS : "fast must do exactly EXPRS ops"; pass++; // Correctness: both should detect overlap (exprList[0] is in idList) assert checkColumnOverlapSlow(List.of("a","b","c"), List.of("x","b")) > 0 : "slow should find overlap"; pass++; assert checkColumnOverlapFast(List.of("a","b","c"), List.of("x","b")) > 0 : "fast should find overlap"; pass++; System.out.printf("%d/4 PASS — sqlite-0001: CWE-407 in checkColumnOverlap%n", pass); System.out.printf("Hotpath: trigger evaluation on every INSERT/UPDATE matching watched table%n"); } }