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 270–278 * * 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 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 defectiveSort( java.util.List parameterOrder, java.util.List resultMappings) { final java.util.List ordered = new java.util.ArrayList<>(parameterOrder); java.util.List 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 fixedSort( java.util.List parameterOrder, java.util.List resultMappings) { // Pre-build index map: O(P) once final java.util.Map index = new java.util.HashMap<>(); int i = 0; for (String p : parameterOrder) { index.put(p, i++); } java.util.List 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 params = java.util.Arrays.asList("id", "name", "age", "email"); // Result mappings come in shuffled order java.util.List mappings = java.util.Arrays.asList("age", "id", "email", "name"); java.util.List defectiveResult = defectiveSort(params, mappings); java.util.List 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 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 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"; } }