java-topology/defects/r-source/unit/RSourceWalkClassGraphTest.java

128 lines
4.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.*;
/**
* RSourceWalkClassGraphTest — CWE-407 test for r-source-0002
*
* Models .walkClassGraph() dedup logic:
* slow: match(names(exti), what) — O(S²) total across loop
* fast: hash-env membership — O(S) total
*
* Test: for S superclasses, slow does ~S*(S+1)/2 comparisons; fast does ~S.
* Ratio must be >= 5x at S=50.
*/
public class RSourceWalkClassGraphTest {
// --- SLOW: O(S²) ---
// match(names(exti), what): linear scan of what for each name in exti
static class SlowWalkClassGraph {
long comparisons = 0;
// Simulate .walkClassGraph accumulating superclasses
// Each iteration adds one new class with a chain of transitive supers
Set<String> walkGraph(List<String[]> classChains) {
// ext = accumulated known superclass names (as ordered list like R's named list)
List<String> what = new ArrayList<>();
for (String[] chain : classChains) {
// exti = chain of superclasses for this intermediate class
// Remove already-known: match(names(exti), what) — O(|chain| × |what|)
List<String> newOnes = new ArrayList<>();
for (String name : chain) {
boolean found = false;
for (String known : what) { // O(|what|) linear scan
comparisons++;
if (known.equals(name)) {
found = true;
break;
}
}
if (!found) newOnes.add(name);
}
what.addAll(newOnes);
}
return new LinkedHashSet<>(what);
}
}
// --- FAST: O(S) ---
// Hash-env membership: exists(name, envir=what_set)
static class FastWalkClassGraph {
long lookups = 0;
Set<String> walkGraph(List<String[]> classChains) {
Set<String> whatSet = new HashSet<>(); // the hash env
List<String> what = new ArrayList<>(); // ordered for reproducibility
for (String[] chain : classChains) {
// exti[!vapply(names(exti), exists, ...)] — O(|chain|)
List<String> newOnes = new ArrayList<>();
for (String name : chain) {
lookups++;
if (!whatSet.contains(name)) {
newOnes.add(name);
}
}
for (String name : newOnes) {
whatSet.add(name);
what.add(name);
}
}
return new LinkedHashSet<>(what);
}
}
// Build a diamond-heavy class hierarchy:
// S classes C_0..C_S-1, each inheriting from multiple earlier classes
static List<String[]> buildClassChains(int s) {
// C_i has transitive supers: C_0 through C_{i-1}
// walkClassGraph processes each intermediate
List<String[]> chains = new ArrayList<>();
for (int i = 0; i < s; i++) {
// The i-th class contributes i transitive superclasses
String[] chain = new String[i];
for (int j = 0; j < i; j++) {
chain[j] = "class_" + j;
}
chains.add(chain);
}
return chains;
}
public static void main(String[] args) {
int[] sizes = {10, 20, 50, 100};
System.out.println("RSourceWalkClassGraphTest — r-source-0002");
System.out.println(" Pattern: match(names(exti), what) O(S²) vs hash-env O(S)");
System.out.println();
int passed = 0;
int total = 0;
for (int s : sizes) {
List<String[]> chains = buildClassChains(s);
SlowWalkClassGraph slow = new SlowWalkClassGraph();
FastWalkClassGraph fast = new FastWalkClassGraph();
Set<String> slowResult = slow.walkGraph(chains);
Set<String> fastResult = fast.walkGraph(chains);
boolean sameResult = slowResult.equals(fastResult);
double ratio = slow.comparisons > 0 ? (double) slow.comparisons / Math.max(fast.lookups, 1) : 1.0;
boolean correctRatio = s >= 20 ? ratio >= 5.0 : ratio >= 2.0;
boolean pass = sameResult && correctRatio;
total++;
if (pass) passed++;
System.out.printf(" S=%-4d slow=%7d fast=%5d ratio=%5.1fx same=%b %s%n",
s, slow.comparisons, fast.lookups, ratio, sameResult,
pass ? "PASS" : "FAIL");
}
System.out.println();
System.out.printf("Result: %d/%d PASS%n", passed, total);
if (passed < total) System.exit(1);
}
}