java-topology/defects/hibernate/unit/HibernateAliasClosureTest.java

184 lines
7.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)");
}
}