5.2 KiB
5.2 KiB
UNDF: UNDF-2026-000000417
hibernate-0006: AbstractEntityPersister — O(T²) alias dedup in subclass property closure
CWE-407 — Algorithmic Complexity: Linear Membership Test in Loop
| Field | Value |
|---|---|
| ID | hibernate-0006 |
| Severity | MEDIUM |
| Ecosystem | hibernate-orm |
| Package | hibernate-core |
| File | hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java |
| Lines | 665–709 |
| Complexity | O(T²) where T = total columns across subclass property closure |
| Fix | Replace ArrayList<String> with LinkedHashSet<String> for alias dedup |
Defective Code
// Lines 665-709 in AbstractEntityPersister constructor
final ArrayList<String> aliases = new ArrayList<>(); // O(N) contains
final ArrayList<String> formulaAliases = new ArrayList<>(); // O(N) contains
for (var prop : persistentClass.getSubclassPropertyClosure()) { // outer: O(P properties)
...
for (int i = 0; i < selectables.size(); i++) { // inner: O(C columns/prop)
...
final String columnAlias = selectable.getAlias(...);
if (prop.isSelectable() && !aliases.contains(columnAlias)) { // O(T) — CWE-407
aliases.add(columnAlias);
}
...
final String formulaAlias = selectable.getAlias(dialect);
if (prop.isSelectable() && !formulaAliases.contains(formulaAlias)) { // O(F) — CWE-407
formulaAliases.add(formulaAlias);
}
}
}
subclassColumnAliasClosure = toStringArray(aliases);
subclassFormulaAliasClosure = toStringArray(formulaAliases);
Pattern: For each column selectable (total T across all subclass properties), calls
ArrayList.contains() on a list that grows to size T. Total cost: O(T²).
For table-per-hierarchy (TPH) inheritance with many subclasses (e.g., 50 subclasses × 20
columns each = 1000 total selectables), this creates a 1,000,000 operation dedup phase
during SessionFactory startup.
Complexity Table
| T (total subclass columns) | Operations (before) | Operations (after) |
|---|---|---|
| 100 | ~10,000 | ~100 |
| 500 | ~250,000 | ~500 |
| 1,000 | ~1,000,000 | ~1,000 |
Speedup ratio: ~1000x at T=1000.
Fix
// hibernate-0006 fix: LinkedHashSet for O(1) contains() + insertion-order preservation.
// Previously ArrayList<String>: each aliases.contains() is O(T) in a loop over T columns
// → O(T²) total. LinkedHashSet.add() is O(1) amortized and handles dedup automatically.
final LinkedHashSet<String> aliases = new LinkedHashSet<>();
final LinkedHashSet<String> formulaAliases = new LinkedHashSet<>();
for (var prop : persistentClass.getSubclassPropertyClosure()) {
...
for (int i = 0; i < selectables.size(); i++) {
...
if (selectable instanceof Formula) {
...
if (prop.isSelectable()) {
formulaAliases.add(formulaAlias); // O(1) — dedup handled by set
}
}
else if (selectable instanceof Column column) {
...
if (prop.isSelectable()) {
aliases.add(columnAlias); // O(1) — dedup handled by set
}
}
}
}
subclassColumnAliasClosure = toStringArray(new ArrayList<>(aliases));
subclassFormulaAliasClosure = toStringArray(new ArrayList<>(formulaAliases));
Patch
--- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java
+++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java
@@ -xxx import section +++
+import java.util.LinkedHashSet;
@@ -664,8 +664,11 @@ public abstract class AbstractEntityPersister
// SUBCLASS PROPERTY CLOSURE
- final ArrayList<String> aliases = new ArrayList<>();
- final ArrayList<String> formulaAliases = new ArrayList<>();
+ // hibernate-0006 fix: LinkedHashSet for O(1) dedup; was O(T²) with ArrayList.contains()
+ // where T = total columns across the subclass property closure.
+ final LinkedHashSet<String> aliasSet = new LinkedHashSet<>();
+ final LinkedHashSet<String> formulaAliasSet = new LinkedHashSet<>();
...
@@ -699,10 +703,10 @@ public abstract class AbstractEntityPersister
if ( selectable instanceof Formula ) {
...
- if ( prop.isSelectable() && !formulaAliases.contains( formulaAlias ) ) {
- formulaAliases.add( formulaAlias );
+ if ( prop.isSelectable() ) {
+ formulaAliasSet.add( formulaAlias ); // O(1)
}
}
else if ( selectable instanceof Column column ) {
...
- if ( prop.isSelectable() && !aliases.contains( columnAlias ) ) {
- aliases.add( columnAlias );
+ if ( prop.isSelectable() ) {
+ aliasSet.add( columnAlias ); // O(1)
}
...
}
}
}
- subclassColumnAliasClosure = toStringArray( aliases );
- subclassFormulaAliasClosure = toStringArray( formulaAliases );
+ subclassColumnAliasClosure = toStringArray( new ArrayList<>( aliasSet ) );
+ subclassFormulaAliasClosure = toStringArray( new ArrayList<>( formulaAliasSet ) );