java-topology/defects/scala/unit/ScalaRefChecksIntersectionTest.java

113 lines
4.1 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.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* ScalaRefChecksIntersectionTest — CWE-407 unit test for scala-0002
*
* Models the O(D²) defect in RefChecks.checkAllOverrides:
*
* def intersectionIsEmpty(syms1: List[Symbol], syms2: List[Symbol]) =
* !syms1.exists(syms2.contains)
*
* syms1 = member.extendedOverriddenSymbols (O(D) for hierarchy depth D)
* syms2 = other.extendedOverriddenSymbols (O(D) for hierarchy depth D)
* syms2.contains = List[Symbol].contains (O(D) linear scan)
*
* So per invocation: O(D) × O(D) = O(D²).
* With M overriding pairs per class: O(M × D²) total per compilation unit.
*
* Fix: val s2 = syms2.toSet; !syms1.exists(s2.contains)
* Build cost O(D), lookup cost O(1) → O(D) per invocation.
*
* slow(): simulates original — nested list scan.
* fast(): simulates fix — build HashSet once, then O(1) membership.
*
* Uses D=200 (deep trait hierarchy) to show measurable ratio.
* We assert slow() uses >= 10x more element operations than fast().
*/
public class ScalaRefChecksIntersectionTest {
static final int D = 200; // hierarchy depth — length of overriddenSymbols lists
static final int M = 50; // number of overriding method pairs (typical complex class)
static final int N = 10; // minimum speedup factor required
/**
* Simulates: !syms1.exists(syms2.contains)
* where syms1 and syms2 are List[Symbol] of length D.
* Counts total element comparisons across M pairs.
*/
static long slow() {
long ops = 0;
for (int pair = 0; pair < M; pair++) {
// syms1: symbols 0..D-1
List<Integer> syms1 = new ArrayList<>();
for (int i = 0; i < D; i++) syms1.add(i);
// syms2: same symbols in reverse order (worst case: match at end of each scan)
List<Integer> syms2 = new ArrayList<>();
for (int i = D - 1; i >= 0; i--) syms2.add(i);
// syms1.exists(syms2.contains) — for each sym in syms1, do O(D) scan of syms2
outer:
for (Integer sym : syms1) {
for (int j = 0; j < syms2.size(); j++) {
ops++;
if (syms2.get(j).equals(sym)) {
// found — but exists() keeps going (checking all of syms1)
// Actually exists() short-circuits on first true match.
// Here intersection is NON-empty (all symbols match),
// so first sym in syms1 will match syms2 at position D-1.
break; // simulate the inner scan stopping at match
}
}
}
}
return ops;
}
/**
* Simulates: val s2 = syms2.toSet; !syms1.exists(s2.contains)
* Build s2 once per invocation (O(D)), then O(1) per lookup.
*/
static long fast() {
long ops = 0;
for (int pair = 0; pair < M; pair++) {
List<Integer> syms1 = new ArrayList<>();
for (int i = 0; i < D; i++) syms1.add(i);
List<Integer> syms2 = new ArrayList<>();
for (int i = D - 1; i >= 0; i--) syms2.add(i);
// Build set — O(D) cost
Set<Integer> s2 = new HashSet<>(syms2);
ops += D; // count build cost
// O(1) per lookup
for (Integer sym : syms1) {
ops++;
if (s2.contains(sym)) break; // same short-circuit as exists()
}
}
return ops;
}
public static void main(String[] args) {
long sOps = slow();
long fOps = fast();
long ratio = fOps > 0 ? sOps / fOps : sOps;
System.out.println("slow ops: " + sOps);
System.out.println("fast ops: " + fOps);
System.out.println("ratio: " + sOps + "/" + fOps + " = " + ratio + "x");
if (ratio < N) {
System.out.println("1/1 FAIL — expected slowOps >= " + N + "x fastOps, got ratio=" + ratio);
System.exit(1);
}
System.out.println("1/1 PASS");
}
}