ORM wave: 24 defects patched across 10 ORMs (157 sites, 62 ecosystems)

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
This commit is contained in:
russell@unturf.com 2026-03-27 13:34:26 -04:00
parent db2986ae44
commit d4ed2dff91
49 changed files with 4025 additions and 7 deletions

View file

@ -0,0 +1,124 @@
package unit;
/**
* Regression test for mybatis-0001: CWE-407 O(n² log n) sort comparator
* in ResultMappingConstructorResolver.sortConstructorMappings().
*
* File: src/main/java/org/apache/ibatis/builder/ResultMappingConstructorResolver.java
* Lines 270278
*
* The sort comparator calls ArrayList.indexOf(o.getProperty()) for both elements
* on every comparison. indexOf() is O(P) (P = constructor parameter count).
* A comparison sort calls the comparator O(N log N) times, giving O(N * P * log N)
* total quadratic in combined size when N P.
*
* Fix: pre-build a Map<String,Integer> before sorting; comparator becomes O(1),
* reducing total to O(N log N).
*/
public class MyBatisConstructorSortTest {
// ---- Defective sort (mirrors MyBatis before fix) ----
static java.util.List<String> defectiveSort(
java.util.List<String> parameterOrder,
java.util.List<String> resultMappings) {
final java.util.List<String> ordered = new java.util.ArrayList<>(parameterOrder);
java.util.List<String> copy = new java.util.ArrayList<>(resultMappings);
copy.sort((o1, o2) -> {
// O(P) each CWE-407
int idx1 = ordered.indexOf(o1);
int idx2 = ordered.indexOf(o2);
return idx1 - idx2;
});
return copy;
}
// ---- Fixed sort (mirrors MyBatis after fix) ----
static java.util.List<String> fixedSort(
java.util.List<String> parameterOrder,
java.util.List<String> resultMappings) {
// Pre-build index map: O(P) once
final java.util.Map<String, Integer> index = new java.util.HashMap<>();
int i = 0;
for (String p : parameterOrder) {
index.put(p, i++);
}
java.util.List<String> copy = new java.util.ArrayList<>(resultMappings);
copy.sort((o1, o2) -> {
// O(1) each
int idx1 = index.getOrDefault(o1, -1);
int idx2 = index.getOrDefault(o2, -1);
return idx1 - idx2;
});
return copy;
}
// ---- Tests ----
public static void main(String[] args) {
testCorrectness();
testPerformance();
System.out.println("All mybatis CWE-407 unit tests passed.");
}
static void testCorrectness() {
java.util.List<String> params = java.util.Arrays.asList("id", "name", "age", "email");
// Result mappings come in shuffled order
java.util.List<String> mappings = java.util.Arrays.asList("age", "id", "email", "name");
java.util.List<String> defectiveResult = defectiveSort(params, mappings);
java.util.List<String> fixedResult = fixedSort(params, mappings);
// Both should produce same canonical parameter order
assert defectiveResult.equals(params)
: "Defective: expected " + params + " got " + defectiveResult;
assert fixedResult.equals(params)
: "Fixed: expected " + params + " got " + fixedResult;
System.out.println(" [PASS] constructor sort correctness: " + fixedResult);
}
static void testPerformance() {
final int P = 500; // constructor parameters
final int N = 500; // result mappings (N P = worst case)
final int REPS = 50; // repetitions to stabilise timing
java.util.List<String> params = new java.util.ArrayList<>(P);
for (int i = 0; i < P; i++) {
params.add("param_" + i);
}
// Shuffle result mappings to force real comparisons
java.util.List<String> mappings = new java.util.ArrayList<>(params.subList(0, N));
java.util.Collections.shuffle(mappings, new java.util.Random(42));
// Defective: O(N * P * log N)
long t0 = System.nanoTime();
for (int r = 0; r < REPS; r++) {
defectiveSort(params, mappings);
}
long defectiveNs = (System.nanoTime() - t0) / REPS;
// Fixed: O(N log N)
t0 = System.nanoTime();
for (int r = 0; r < REPS; r++) {
fixedSort(params, mappings);
}
long fixedNs = (System.nanoTime() - t0) / REPS;
double speedup = (double) defectiveNs / fixedNs;
System.out.printf(" [PERF] N=P=%d defective=%.2fms fixed=%.2fms speedup=%.1fx%n",
P,
defectiveNs / 1_000_000.0,
fixedNs / 1_000_000.0,
speedup);
// Expect meaningful speedup: defective is O(N*P*log N) O(N² log N),
// fixed is O(N log N), so speedup should be >> 1 at N=P=500
assert speedup > 3.0
: "Expected >3x speedup, got " + speedup + "x — fix may not be effective";
}
}