package unit; import java.util.*; /** * MybatisTest — CWE-407 benchmark for mybatis-0001 and mybatis-0002 * * mybatis-0001: ResultMapping.flags List contains() in loop * ResultMap.Builder.build() iterates resultMappings calling * resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR) and * resultMapping.getFlags().contains(ResultFlag.ID) — each call is O(F) * where F = flags per mapping, making the full loop O(N×F). * Fix: change flags from ArrayList to EnumSet — O(1) contains() → O(N) total. * * mybatis-0002: MapperBuilderAssistant.addResultMap() removeIf scan * extendedResultMappings.removeIf(rm -> rm.getFlags().contains(ResultFlag.CONSTRUCTOR)) * Same O(F) per mapping; auto-fixed by mybatis-0001's EnumSet change. */ public class MybatisTest { // Sentinel enum matching ResultFlag's structure: exactly 2 values enum ResultFlag { ID, CONSTRUCTOR } // --------------------------------------------------------------------------- // mybatis-0001: flags contains() in ResultMap build() loop // --------------------------------------------------------------------------- /** * Simulates ResultMap.Builder.build() loop with ArrayList. * * For each of N result mappings, calls flags.contains(CONSTRUCTOR) and * flags.contains(ID) — each is an O(F) linear scan of the flags list. * * Total probes = N * (avgScanLengthForCONSTRUCTOR + avgScanLengthForID) * Worst case (flag not present): N * 2 * F probes. * * @param numMappings N — number of result mappings * @param numFlags F — flags per mapping (worst case: flag not found → full scan) * @return total ArrayList element comparisons executed */ static long slowFlagsContains(int numMappings, int numFlags) { // Build a flag list that does NOT contain CONSTRUCTOR or ID // so every contains() call does a full scan of all F elements List flagTemplate = new ArrayList<>(numFlags); // Fill with alternating ID/CONSTRUCTOR but exclude the search targets // by using an empty list (no flags) — each contains() scans 0 elements // That's trivial; instead we want a list that is populated but misses the target. // Use a list where all F slots are filled with a "wrong" value by repeating // the opposite flag. Since ResultFlag only has 2 values, we'll simulate // arbitrary flag objects using Integer to represent F distinct flag-like tokens. // // Actually: simulate with a List of size F, searching for -1 (not present). // This models the O(F) worst-case scan faithfully. long probes = 0; for (int i = 0; i < numMappings; i++) { // Build flags list for this mapping — F elements, target not present List flags = new ArrayList<>(numFlags); for (int j = 0; j < numFlags; j++) { flags.add(j); // values 0..F-1, none equal to -1 } // Simulate getFlags().contains(CONSTRUCTOR) — full scan (not found) int target = -1; for (int j = 0; j < flags.size(); j++) { probes++; if (flags.get(j).equals(target)) break; } // Simulate getFlags().contains(ID) — second full scan for (int j = 0; j < flags.size(); j++) { probes++; if (flags.get(j).equals(target)) break; } } return probes; } /** * Simulates the fixed version: flags stored as EnumSet (or equivalent Set). * Set.contains() is O(1) — modeled as exactly 1 probe per call. * * @param numMappings N * @param numFlags F (irrelevant for complexity — included for symmetry) * @return total probes: N * 2 (two O(1) lookups per mapping) */ static long fastFlagsContains(int numMappings, int numFlags) { long probes = 0; for (int i = 0; i < numMappings; i++) { // Build flags as a HashSet (models EnumSet) Set flags = new HashSet<>(numFlags * 2); for (int j = 0; j < numFlags; j++) { flags.add(j); } // O(1) contains check — count as 1 probe each probes++; // contains(CONSTRUCTOR) flags.contains(-1); probes++; // contains(ID) flags.contains(-1); } return probes; } // --------------------------------------------------------------------------- // mybatis-0002: removeIf(rm -> rm.getFlags().contains(CONSTRUCTOR)) // --------------------------------------------------------------------------- /** * Simulates addResultMap() removeIf with ArrayList.contains() — O(R×F). * R = result mappings in extendedResultMappings, F = flags per mapping. * * @param numMappings R * @param numFlags F * @return total probes across all removeIf predicate evaluations */ static long slowRemoveIf(int numMappings, int numFlags) { long probes = 0; for (int i = 0; i < numMappings; i++) { // Simulate flags.contains(CONSTRUCTOR) for each mapping — O(F) scan List flags = new ArrayList<>(numFlags); for (int j = 0; j < numFlags; j++) { flags.add(j); } int target = -1; for (int j = 0; j < flags.size(); j++) { probes++; if (flags.get(j).equals(target)) break; } } return probes; } /** * Simulates fixed removeIf with EnumSet.contains() — O(R). * * @param numMappings R * @param numFlags F (irrelevant) * @return total probes: R * 1 */ static long fastRemoveIf(int numMappings, int numFlags) { long probes = 0; for (int i = 0; i < numMappings; i++) { Set flags = new HashSet<>(numFlags * 2); for (int j = 0; j < numFlags; j++) { flags.add(j); } probes++; // O(1) Set.contains flags.contains(-1); } return probes; } // --------------------------------------------------------------------------- // Benchmarking helper // --------------------------------------------------------------------------- static void bench(String label, long slowOps, long fastOps) { double ratio = (double) slowOps / Math.max(fastOps, 1); System.out.printf(" %-66s slow:%,8d ops fast:%,8d ops ratio:%.0fx%n", label, slowOps, fastOps, ratio); } // --------------------------------------------------------------------------- // main // --------------------------------------------------------------------------- public static void main(String[] args) { System.out.println("MybatisTest — CWE-407: mybatis-0001 + mybatis-0002"); System.out.println(); // --- mybatis-0001 benchmarks --- System.out.println(" [mybatis-0001: ResultMap.build() flags.contains() ArrayList O(N×F) → EnumSet O(N)]"); int[][] cases0001 = {{100, 10}, {500, 20}, {1000, 50}}; for (int[] c : cases0001) { int n = c[0], f = c[1]; long slow = slowFlagsContains(n, f); long fast = fastFlagsContains(n, f); bench(String.format("N=%d mappings, F=%d flags", n, f), slow, fast); } System.out.println(); // --- mybatis-0002 benchmarks --- System.out.println(" [mybatis-0002: addResultMap() removeIf flags.contains() ArrayList O(R×F) → EnumSet O(R)]"); int[][] cases0002 = {{100, 10}, {500, 20}, {1000, 50}}; for (int[] c : cases0002) { int r = c[0], f = c[1]; long slow = slowRemoveIf(r, f); long fast = fastRemoveIf(r, f); bench(String.format("R=%d mappings, F=%d flags", r, f), slow, fast); } System.out.println(); // --- assertions --- int pass = 0; int total = 0; // mybatis-0001: at N=1000, F=50 → slow = 1000*2*50 = 100_000; fast = 1000*2 = 2000 → ratio 50x { total++; int n = 1000, f = 50; long slow = slowFlagsContains(n, f); long fast = fastFlagsContains(n, f); double ratio = (double) slow / Math.max(fast, 1); boolean ok = ratio > 10.0; if (ok) pass++; System.out.printf(" %s mybatis-0001: ArrayList.contains O(N×F) → EnumSet O(N)" + " N=%d F=%d slow=%,d fast=%,d ratio=%.0fx%n", ok ? "PASS" : "FAIL", n, f, slow, fast, ratio); if (!ok) { System.err.println(" FAIL mybatis-0001: expected ratio > 10x, got " + ratio); } } // mybatis-0002: at R=1000, F=50 → slow = 50_000; fast = 1000 → ratio 50x { total++; int r = 1000, f = 50; long slow = slowRemoveIf(r, f); long fast = fastRemoveIf(r, f); double ratio = (double) slow / Math.max(fast, 1); boolean ok = ratio > 10.0; if (ok) pass++; System.out.printf(" %s mybatis-0002: removeIf ArrayList.contains O(R×F) → EnumSet O(R)" + " R=%d F=%d slow=%,d fast=%,d ratio=%.0fx%n", ok ? "PASS" : "FAIL", r, f, slow, fast, ratio); if (!ok) { System.err.println(" FAIL mybatis-0002: expected ratio > 10x, got " + ratio); } } System.out.println(); System.out.printf("%d/%d PASS%n", pass, total); if (pass < total) { System.out.println("FAIL"); System.exit(1); } System.out.println("ALL PASS"); } }