package unit; import java.util.*; /** * DoctrineTest — doctrine-0001..0003 * * Proves CWE-407 in Doctrine ORM: * doctrine-0001: AbstractHydrator.gatherRowData() — in_array($disc, $discriminatorValues) O(S) per row * doctrine-0002: ClassMetadata.addSubClass() — in_array($class, $subClasses) O(S) dedup * doctrine-0003: SqlWalker.walkObjectExpression() — in_array($field, $partialFieldSet) O(P) per field * * Run: javac -d . DoctrineTest.java && java -ea unit.DoctrineTest */ public class DoctrineTest { // ── doctrine-0001: AbstractHydrator discriminatorValues per row ─────────── /** * SLOW: in_array($disc, $discriminatorValues) O(S) per row per col. * O(N × C × S) for N rows, C columns with inheritance, S subclasses. */ static long hydratorSlow(int numRows, int numCols, int numSubclasses) { List discriminatorValues = new ArrayList<>(); for (int i = 0; i < numSubclasses; i++) discriminatorValues.add("SubClass" + i); long ops = 0; for (int r = 0; r < numRows; r++) { for (int c = 0; c < numCols; c++) { String disc = "SubClass" + (r % numSubclasses); // valid disc — scan to end for (String v : discriminatorValues) { ops++; if (v.equals(disc)) break; } } } return ops; } /** FAST: array_flip → isset() O(1). O(N × C) total. */ static long hydratorFast(int numRows, int numCols, int numSubclasses) { Set discriminatorValuesSet = new HashSet<>(); for (int i = 0; i < numSubclasses; i++) discriminatorValuesSet.add("SubClass" + i); long ops = 0; for (int r = 0; r < numRows; r++) { for (int c = 0; c < numCols; c++) { String disc = "SubClass" + (r % numSubclasses); ops++; // O(1) discriminatorValuesSet.contains(disc); } } return ops; } // ── doctrine-0002: ClassMetadata.addSubClass() dedup ───────────────────── /** SLOW: in_array($class, $subClasses) O(S) per addSubClass call. O(H×S) total. */ static long addSubClassSlow(int numSubclasses) { List subClasses = new ArrayList<>(); long ops = 0; // Add each class twice (parent factory calls addSubClass for all parents) for (int pass = 0; pass < 2; pass++) { for (int i = 0; i < numSubclasses; i++) { String cls = "SubClass" + i; boolean found = false; for (String s : subClasses) { ops++; if (s.equals(cls)) { found = true; break; } } if (!found) subClasses.add(cls); } } return ops; } /** FAST: isset($subClassesSet[$class]) O(1). O(H) total. */ static long addSubClassFast(int numSubclasses) { Set subClassesSet = new HashSet<>(); List subClasses = new ArrayList<>(); long ops = 0; for (int pass = 0; pass < 2; pass++) { for (int i = 0; i < numSubclasses; i++) { String cls = "SubClass" + i; ops++; if (subClassesSet.add(cls)) subClasses.add(cls); } } return ops; } // ── doctrine-0003: SqlWalker partialFieldSet check ─────────────────────── /** SLOW: in_array($fieldName, $partialFieldSet) O(P) per field — O(F×P) total. */ static long sqlWalkerSlow(int numFields, int partialFieldSetSize) { List partialFieldSet = new ArrayList<>(); for (int i = 0; i < partialFieldSetSize; i++) partialFieldSet.add("field_" + i); long ops = 0; for (int f = 0; f < numFields; f++) { String fieldName = "field_" + (f % (partialFieldSetSize * 2)); for (String p : partialFieldSet) { ops++; if (p.equals(fieldName)) break; } } return ops; } /** FAST: $partialFieldSetFlip[$fieldName] O(1). O(F) total. */ static long sqlWalkerFast(int numFields, int partialFieldSetSize) { Set partialFieldSetFlip = new HashSet<>(); for (int i = 0; i < partialFieldSetSize; i++) partialFieldSetFlip.add("field_" + i); long ops = 0; for (int f = 0; f < numFields; f++) { String fieldName = "field_" + (f % (partialFieldSetSize * 2)); ops++; partialFieldSetFlip.contains(fieldName); } 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 doctrine-0001..0003: Doctrine ORM CWE-407 ==="); System.out.println(); final int ROWS = 2000, COLS = 10, SUBCLASSES = 50; // doctrine-0001 final int NUM_SUBCLASSES = 500; // doctrine-0002 final int FIELDS = 500, PARTIAL = 200; // doctrine-0003 long s0 = hydratorSlow(ROWS, COLS, SUBCLASSES), f0 = hydratorFast(ROWS, COLS, SUBCLASSES); bench("doctrine-0001 Hydrator discriminatorValues in_array", () -> hydratorSlow(ROWS, COLS, SUBCLASSES), () -> hydratorFast(ROWS, COLS, SUBCLASSES), s0, f0); long s1 = addSubClassSlow(NUM_SUBCLASSES), f1 = addSubClassFast(NUM_SUBCLASSES); bench("doctrine-0002 ClassMetadata.addSubClass() dedup", () -> addSubClassSlow(NUM_SUBCLASSES), () -> addSubClassFast(NUM_SUBCLASSES), s1, f1); long s2 = sqlWalkerSlow(FIELDS, PARTIAL), f2 = sqlWalkerFast(FIELDS, PARTIAL); bench("doctrine-0003 SqlWalker partialFieldSet in_array", () -> sqlWalkerSlow(FIELDS, PARTIAL), () -> sqlWalkerFast(FIELDS, PARTIAL), s2, f2); System.out.println(); int pass = 0; assert s0 > f0 * 5 : "doctrine-0001 expected >5x"; pass++; assert s1 > f1 * 5 : "doctrine-0002 expected >5x"; pass++; assert s2 > f2 * 5 : "doctrine-0003 expected >5x"; pass++; System.out.printf("%d/3 PASS — doctrine-0001..0003: CWE-407 in Doctrine ORM%n", pass); System.out.printf("Hotpaths: inheritance query hydration, metadata warmup, PARTIAL DQL queries%n"); } }