package unit; /** * Regression test for hibernate-0001/0002/0003/0004/0005: CWE-407 O(n²) membership * tests in Hibernate ORM mapping layer. * * These tests use a synthetic stand-in (not the real Hibernate classes) to demonstrate * the O(n²) vs O(n) performance difference and confirm the fix semantics. * * Defects confirmed at: * hibernate-0001: Constraint.addColumn() — ArrayList.contains() inside loop * hibernate-0002: ForeignKey.addReferencedColumn() — ArrayList.contains() inside loop * hibernate-0003: Index.addColumn() — ArrayList.contains() inside loop * hibernate-0004: buildRecursiveOrderedFkSecondPasses() — ArrayList.contains()+add(0,...) in recursion * hibernate-0005: orderHierarchy() — ArrayList.contains() in recursive hierarchy sort */ public class HibernateConstraintColumnTest { // ---- Defective model (mirrors Hibernate before fix) ---- static class DefectiveConstraint { private final java.util.ArrayList columns = new java.util.ArrayList<>(); public void addColumn(String column) { if (!columns.contains(column)) { // O(n) — CWE-407 columns.add(column); } } public java.util.List getColumns() { return columns; } } // ---- Fixed model (mirrors Hibernate after fix) ---- static class FixedConstraint { private final java.util.LinkedHashSet columns = new java.util.LinkedHashSet<>(); public void addColumn(String column) { columns.add(column); // O(1) — dedup handled by set } public java.util.List getColumns() { return new java.util.ArrayList<>(columns); } } // ---- Defective FK second-pass ordering ---- static class DefectiveOrderedList { private final java.util.List ordered = new java.util.ArrayList<>(); public void addIfAbsent(String item) { if (!ordered.contains(item)) { // O(n) — CWE-407 ordered.add(0, item); // O(n) prepend } } public java.util.List getOrdered() { return ordered; } } // ---- Fixed FK second-pass ordering ---- static class FixedOrderedList { private final java.util.LinkedHashSet ordered = new java.util.LinkedHashSet<>(); public void addIfAbsent(String item) { ordered.add(item); // O(1) } public java.util.List getOrdered() { return new java.util.ArrayList<>(ordered); } } // ---- Tests ---- public static void main(String[] args) { testConstraintDedup(); testOrderedListDedup(); testPerformance(); System.out.println("All hibernate CWE-407 unit tests passed."); } static void testConstraintDedup() { DefectiveConstraint defective = new DefectiveConstraint(); FixedConstraint fixed = new FixedConstraint(); for (int i = 0; i < 100; i++) { defective.addColumn("col" + (i % 10)); fixed.addColumn("col" + (i % 10)); } // Both should deduplicate to exactly 10 unique columns assert defective.getColumns().size() == 10 : "Defective: expected 10 unique columns, got " + defective.getColumns().size(); assert fixed.getColumns().size() == 10 : "Fixed: expected 10 unique columns, got " + fixed.getColumns().size(); // Both should preserve insertion order of first occurrence assert defective.getColumns().get(0).equals("col0") : "Defective: wrong order"; assert fixed.getColumns().get(0).equals("col0") : "Fixed: wrong order"; System.out.println(" [PASS] constraint column dedup (defective=" + defective.getColumns().size() + " fixed=" + fixed.getColumns().size() + ")"); } static void testOrderedListDedup() { DefectiveOrderedList defective = new DefectiveOrderedList(); FixedOrderedList fixed = new FixedOrderedList(); String[] items = {"table_c", "table_a", "table_b", "table_c", "table_a", "table_d"}; for (String item : items) { defective.addIfAbsent(item); fixed.addIfAbsent(item); } // Both should deduplicate to 4 unique tables assert defective.getOrdered().size() == 4 : "Defective: expected 4 unique items, got " + defective.getOrdered().size(); assert fixed.getOrdered().size() == 4 : "Fixed: expected 4 unique items, got " + fixed.getOrdered().size(); System.out.println(" [PASS] FK second-pass dedup (defective=" + defective.getOrdered().size() + " fixed=" + fixed.getOrdered().size() + ")"); } static void testPerformance() { final int N = 5000; // Defective: O(n²) — each addColumn scans the whole list DefectiveConstraint defective = new DefectiveConstraint(); long t0 = System.nanoTime(); for (int i = 0; i < N; i++) { defective.addColumn("column_" + i); } // Re-add all to trigger the contains() scans on a full list for (int i = 0; i < N; i++) { defective.addColumn("column_" + i); } long defectiveNs = System.nanoTime() - t0; // Fixed: O(n) — LinkedHashSet add() is O(1) FixedConstraint fixed = new FixedConstraint(); t0 = System.nanoTime(); for (int i = 0; i < N; i++) { fixed.addColumn("column_" + i); } for (int i = 0; i < N; i++) { fixed.addColumn("column_" + i); } long fixedNs = System.nanoTime() - t0; double speedup = (double) defectiveNs / fixedNs; System.out.printf(" [PERF] N=%d defective=%.1fms fixed=%.1fms speedup=%.1fx%n", N, defectiveNs / 1_000_000.0, fixedNs / 1_000_000.0, speedup); // Expect at least 5× speedup at N=5000 due to O(n²) vs O(n) assert speedup > 5.0 : "Expected >5x speedup, got " + speedup + "x — fix may not be effective"; } }