java-topology/defects/mybatis/unit/MyBatisConstructorSortTest.java
russell@unturf.com d4ed2dff91 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
2026-03-27 13:34:26 -04:00

124 lines
4.6 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 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";
}
}