# 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

```java
// 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

```java
// 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

```diff
--- 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 ) );
```
