Hibernate (5 HIGH): addColumn/addReferencedColumn/addIndex ArrayList→LinkedHashSet (19x) FK second-pass LinkedHashSet, orderHierarchy LinkedHashSet MyBatis (1 MEDIUM): sortConstructorMappings indexOf→HashMap (12x) EF Core (2 HIGH + 1 MEDIUM): FindGenerationProperty HashSet (250x), AddPrincipals HashSet (250x), FK discovery HashSet (6x) Diesel (3 MEDIUM): SQLite/MySQL row position()→BTreeMap (51x) SQLAlchemy (2 HIGH): _values_bindparam Set (500x), evaluated_keys Set (500x) Peewee (1 MEDIUM): _SortedFieldList.index() bisect (42x) Sequelize (2 HIGH): bulkInsert Set (50x), expandIncludeAll Set (250x) TypeORM (3 HIGH): OrmUtils.uniq Map (500x), diffColumns Set (125x), updatedColumns Set (100x) Doctrine ORM (1 HIGH + 2 MEDIUM): hydrator discriminator (26x), addSubClass (250x), SqlWalker partial (130x) GORM (1 MEDIUM): sortCallbacks getRIndex→map (194x) SQLite: SqliteTest unit proof 4/4 PASS (101x) Unit tests: all PASS — Hibernate/MyBatis/EfCore/Diesel/SQLAlchemy/Peewee/ Sequelize/TypeORM/Doctrine/GORM Whitepaper: 157 sites, 62 ecosystems; PDF 752K
168 lines
6.1 KiB
Java
168 lines
6.1 KiB
Java
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<String> 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<String> getColumns() {
|
||
return columns;
|
||
}
|
||
}
|
||
|
||
// ---- Fixed model (mirrors Hibernate after fix) ----
|
||
|
||
static class FixedConstraint {
|
||
private final java.util.LinkedHashSet<String> columns = new java.util.LinkedHashSet<>();
|
||
|
||
public void addColumn(String column) {
|
||
columns.add(column); // O(1) — dedup handled by set
|
||
}
|
||
|
||
public java.util.List<String> getColumns() {
|
||
return new java.util.ArrayList<>(columns);
|
||
}
|
||
}
|
||
|
||
// ---- Defective FK second-pass ordering ----
|
||
|
||
static class DefectiveOrderedList {
|
||
private final java.util.List<String> 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<String> getOrdered() {
|
||
return ordered;
|
||
}
|
||
}
|
||
|
||
// ---- Fixed FK second-pass ordering ----
|
||
|
||
static class FixedOrderedList {
|
||
private final java.util.LinkedHashSet<String> ordered = new java.util.LinkedHashSet<>();
|
||
|
||
public void addIfAbsent(String item) {
|
||
ordered.add(item); // O(1)
|
||
}
|
||
|
||
public java.util.List<String> 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";
|
||
}
|
||
}
|