java-topology/defects/mybatis/unit/MybatisTest.java

243 lines
9.7 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;
import java.util.*;
/**
* MybatisTest — CWE-407 benchmark for mybatis-0001 and mybatis-0002
*
* mybatis-0001: ResultMapping.flags List<ResultFlag> 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<ResultFlag>.
*
* 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<ResultFlag> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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");
}
}