package unit; /** * Regression test for hibernate-0006: CWE-407 O(T²) alias deduplication in * AbstractEntityPersister subclass property closure initialization. * * File: hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java * Lines: 665-709 * * Defect: aliases and formulaAliases are ArrayList. Inside nested loops over * subclass properties and their selectables, aliases.contains(columnAlias) is O(T) * where T is the growing list of accumulated aliases. For T total selectables, total * cost is O(T²). * * Fix: use LinkedHashSet — O(1) add() handles dedup; insertion order preserved. */ public class HibernateAliasClosureTest { // ---- Defective model (mirrors AbstractEntityPersister before fix) ---- static class DefectivePersisterInit { private final java.util.ArrayList aliases = new java.util.ArrayList<>(); private final java.util.ArrayList formulaAliases = new java.util.ArrayList<>(); private long ops = 0; /** * Simulate processing one selectable column from the subclass property closure. */ void addColumnAlias(String alias) { ops += aliases.size(); // simulate O(N) contains scan if (!aliases.contains(alias)) { // O(N) — CWE-407 aliases.add(alias); } } void addFormulaAlias(String alias) { ops += formulaAliases.size(); if (!formulaAliases.contains(alias)) { // O(N) — CWE-407 formulaAliases.add(alias); } } java.util.List getColumnAliases() { return aliases; } java.util.List getFormulaAliases() { return formulaAliases; } long getOps() { return ops; } } // ---- Fixed model (mirrors AbstractEntityPersister after fix) ---- static class FixedPersisterInit { private final java.util.LinkedHashSet aliases = new java.util.LinkedHashSet<>(); private final java.util.LinkedHashSet formulaAliases = new java.util.LinkedHashSet<>(); private long ops = 0; void addColumnAlias(String alias) { ops++; // O(1) amortized HashSet.add() aliases.add(alias); } void addFormulaAlias(String alias) { ops++; formulaAliases.add(alias); } java.util.List getColumnAliases() { return new java.util.ArrayList<>(aliases); } java.util.List getFormulaAliases() { return new java.util.ArrayList<>(formulaAliases); } long getOps() { return ops; } } // ---- Tests ---- public static void main(String[] args) { testDedup(); testInsertionOrder(); testPerformance(); System.out.println("3/3 PASS"); } static void testDedup() { DefectivePersisterInit defective = new DefectivePersisterInit(); FixedPersisterInit fixed = new FixedPersisterInit(); // Simulate subclass hierarchy: 5 subclasses each contributing 4 columns, // but 2 of those columns are shared (inherited from superclass). // Expected unique aliases: 5 * 2 + 2 = 12 for (int sub = 0; sub < 5; sub++) { defective.addColumnAlias("shared_col1_"); // shared — should dedup defective.addColumnAlias("shared_col2_"); // shared — should dedup defective.addColumnAlias("sub" + sub + "_col1_"); defective.addColumnAlias("sub" + sub + "_col2_"); fixed.addColumnAlias("shared_col1_"); fixed.addColumnAlias("shared_col2_"); fixed.addColumnAlias("sub" + sub + "_col1_"); fixed.addColumnAlias("sub" + sub + "_col2_"); } int expectedUnique = 2 + 5 * 2; // 2 shared + 10 subclass-specific int defectiveCount = defective.getColumnAliases().size(); int fixedCount = fixed.getColumnAliases().size(); if (defectiveCount != expectedUnique) { System.err.println("FAIL testDedup: defective expected " + expectedUnique + " got " + defectiveCount); System.exit(1); } if (fixedCount != expectedUnique) { System.err.println("FAIL testDedup: fixed expected " + expectedUnique + " got " + fixedCount); System.exit(1); } System.out.println(" [PASS] alias dedup: expected=" + expectedUnique + " defective=" + defectiveCount + " fixed=" + fixedCount); } static void testInsertionOrder() { // Fixed (LinkedHashSet) must preserve insertion order of first occurrence FixedPersisterInit fixed = new FixedPersisterInit(); fixed.addColumnAlias("alpha_"); fixed.addColumnAlias("beta_"); fixed.addColumnAlias("gamma_"); fixed.addColumnAlias("alpha_"); // duplicate — should not affect order fixed.addColumnAlias("beta_"); // duplicate java.util.List result = fixed.getColumnAliases(); if (result.size() != 3) { System.err.println("FAIL testInsertionOrder: expected 3, got " + result.size()); System.exit(1); } if (!result.get(0).equals("alpha_") || !result.get(1).equals("beta_") || !result.get(2).equals("gamma_")) { System.err.println("FAIL testInsertionOrder: wrong order: " + result); System.exit(1); } System.out.println(" [PASS] insertion order preserved: " + result); } static void testPerformance() { // Simulate a large TPH hierarchy: 50 subclasses × 20 columns = 1000 selectables, // with the first 5 columns shared (inherited) across all subclasses. final int SUBCLASSES = 50; final int COLS_PER_SUB = 20; final int SHARED_COLS = 5; DefectivePersisterInit defective = new DefectivePersisterInit(); long t0 = System.nanoTime(); for (int sub = 0; sub < SUBCLASSES; sub++) { for (int col = 0; col < SHARED_COLS; col++) { defective.addColumnAlias("shared_" + col + "_"); } for (int col = SHARED_COLS; col < COLS_PER_SUB; col++) { defective.addColumnAlias("sub" + sub + "_col" + col + "_"); } } long slowTime = System.nanoTime() - t0; long slowOps = defective.getOps(); FixedPersisterInit fixed = new FixedPersisterInit(); t0 = System.nanoTime(); for (int sub = 0; sub < SUBCLASSES; sub++) { for (int col = 0; col < SHARED_COLS; col++) { fixed.addColumnAlias("shared_" + col + "_"); } for (int col = SHARED_COLS; col < COLS_PER_SUB; col++) { fixed.addColumnAlias("sub" + sub + "_col" + col + "_"); } } long fastTime = System.nanoTime() - t0; long fastOps = fixed.getOps(); double opRatio = fastOps > 0 ? (double) slowOps / fastOps : 1.0; double timeRatio = fastTime > 0 ? (double) slowTime / fastTime : 1.0; System.out.printf(" [PERF] subclasses=%d cols=%d total=%d slow_ops=%d fast_ops=%d op_ratio=%.1fx time_ratio=%.1fx%n", SUBCLASSES, COLS_PER_SUB, SUBCLASSES * COLS_PER_SUB, slowOps, fastOps, opRatio, timeRatio); if (opRatio < 5.0) { System.err.println("FAIL testPerformance: op ratio " + opRatio + "x < 5x minimum"); System.exit(1); } System.out.println(" [PASS] performance: " + String.format("%.1f", opRatio) + "x op ratio (>= 5x required)"); } }