243 lines
9.7 KiB
Java
243 lines
9.7 KiB
Java
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");
|
||
}
|
||
}
|