184 lines
7.5 KiB
Java
184 lines
7.5 KiB
Java
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<String>. 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<String> — 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<String> aliases = new java.util.ArrayList<>();
|
||
private final java.util.ArrayList<String> 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<String> getColumnAliases() { return aliases; }
|
||
java.util.List<String> getFormulaAliases() { return formulaAliases; }
|
||
long getOps() { return ops; }
|
||
}
|
||
|
||
// ---- Fixed model (mirrors AbstractEntityPersister after fix) ----
|
||
|
||
static class FixedPersisterInit {
|
||
private final java.util.LinkedHashSet<String> aliases = new java.util.LinkedHashSet<>();
|
||
private final java.util.LinkedHashSet<String> 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<String> getColumnAliases() { return new java.util.ArrayList<>(aliases); }
|
||
java.util.List<String> 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<String> 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)");
|
||
}
|
||
}
|