java-topology/defects/spidermonkey/unit/SpiderMonkeyModulesTest.java

339 lines
13 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.HashMap;
import java.util.HashSet;
/**
* SpiderMonkeyModulesTest
*
* Models the CWE-407 defect in js/src/vm/Modules.cpp:
*
* SM-0004 (PRIMARY):
* ModuleGetExportedNames() — ContainsElement(exportedNames, name)
* exportedNames is a GCVector (linear array); the check is called for
* every name in every star-exported module's name list.
*
* With E star-export modules each exporting S names, and exportedNames
* growing toward E×S items, the dedup cost is O(E² × S²) vs O(E×S)
* with a HashSet shadow.
*
* SM-0004 (SECONDARY):
* GatherAvailableModuleAncestors() — ContainsElement(execList, m)
* execList is a ModuleVector (linear); checked for each async parent
* module. O(L×P) vs O(P) with a HashSet.
*
* Run: javac -d . SpiderMonkeyModulesTest.java && java -ea unit.SpiderMonkeyModulesTest
*/
public class SpiderMonkeyModulesTest {
// -----------------------------------------------------------------------
// Defective: ContainsElement(list, name) — linear scan
// Models ExportNameVector (ArrayList) with pointer equality
// -----------------------------------------------------------------------
static boolean defectiveContains(ArrayList<Integer> list, int atom) {
for (int a : list) {
if (a == atom) return true;
}
return false;
}
/**
* Simulate ModuleGetExportedNames — defective path.
*
* @param starModules array of star-export modules, each a list of export name IDs
* @param localExports initial local exports to seed the exportedNames list
* @return op count (total comparisons in ContainsElement calls)
*/
static long defectiveGetExportedNames(int[][] starModules, int[] localExports) {
ArrayList<Integer> exportedNames = new ArrayList<>();
long comparisons = 0;
// Add local/indirect exports (no dedup needed, assume unique)
for (int name : localExports) {
exportedNames.add(name);
}
// For each star-export module, add its names if not already present
for (int[] starNames : starModules) {
for (int name : starNames) {
if (name == -1) continue; // -1 models "default" (skipped)
// O(N) scan — the defect
boolean found = false;
for (int existing : exportedNames) {
comparisons++;
if (existing == name) { found = true; break; }
}
if (!found) {
exportedNames.add(name);
}
}
}
return comparisons;
}
/**
* Simulate ModuleGetExportedNames — fixed path.
* Uses a HashSet shadow for O(1) dedup.
*/
static long fixedGetExportedNames(int[][] starModules, int[] localExports) {
ArrayList<Integer> exportedNames = new ArrayList<>();
HashSet<Integer> exportedSet = new HashSet<>();
long lookups = 0;
for (int name : localExports) {
exportedNames.add(name);
exportedSet.add(name);
}
for (int[] starNames : starModules) {
for (int name : starNames) {
if (name == -1) continue;
lookups++;
if (!exportedSet.contains(name)) {
exportedNames.add(name);
exportedSet.add(name);
}
}
}
return lookups;
}
// -----------------------------------------------------------------------
// Secondary: GatherAvailableModuleAncestors — ContainsElement(execList, m)
// -----------------------------------------------------------------------
static long defectiveGatherAncestors(int[][] parentGraph, int startModule) {
ArrayList<Integer> execList = new ArrayList<>();
long comparisons = 0;
// BFS using stack to simulate recursive gather
ArrayList<Integer> pending = new ArrayList<>();
pending.add(startModule);
while (!pending.isEmpty()) {
int module = pending.remove(pending.size() - 1);
int[] parents = (module < parentGraph.length) ? parentGraph[module] : new int[0];
for (int parent : parents) {
// ContainsElement(execList, parent) — O(L) linear
boolean found = false;
for (int m : execList) {
comparisons++;
if (m == parent) { found = true; break; }
}
if (!found) {
execList.add(parent);
pending.add(parent);
}
}
}
return comparisons;
}
static long fixedGatherAncestors(int[][] parentGraph, int startModule) {
HashSet<Integer> execSet = new HashSet<>();
ArrayList<Integer> pending = new ArrayList<>();
long lookups = 0;
pending.add(startModule);
while (!pending.isEmpty()) {
int module = pending.remove(pending.size() - 1);
int[] parents = (module < parentGraph.length) ? parentGraph[module] : new int[0];
for (int parent : parents) {
lookups++;
if (!execSet.contains(parent)) {
execSet.add(parent);
pending.add(parent);
}
}
}
return lookups;
}
// -----------------------------------------------------------------------
// Helper: build a barrel-file scenario
// E star-export modules, each exporting S unique names,
// with some overlap between modules (last S/4 names repeated)
// -----------------------------------------------------------------------
static int[][] buildStarModules(int E, int S) {
int[][] modules = new int[E][S];
for (int e = 0; e < E; e++) {
for (int s = 0; s < S; s++) {
// First 3*S/4 names are unique per module; last S/4 overlap
if (s < (S * 3 / 4)) {
modules[e][s] = e * S + s;
} else {
modules[e][s] = 1_000_000 + s; // shared across modules
}
}
}
return modules;
}
// -----------------------------------------------------------------------
// Test 1 — correctness: both paths produce same exported names set size
// -----------------------------------------------------------------------
static void test1_correctness() {
int E = 3, S = 10;
int[][] starModules = buildStarModules(E, S);
int[] localExports = {9001, 9002, 9003};
// Count unique names expected
HashSet<Integer> expected = new HashSet<>();
for (int n : localExports) expected.add(n);
for (int[] sm : starModules) for (int n : sm) if (n != -1) expected.add(n);
// Verify defective path (correctness only — count by rebuilding)
ArrayList<Integer> defectResult = new ArrayList<>();
HashSet<Integer> defectSet = new HashSet<>();
for (int n : localExports) { defectResult.add(n); defectSet.add(n); }
for (int[] sm : starModules) {
for (int name : sm) {
if (name == -1) continue;
if (!defectSet.contains(name)) {
defectResult.add(name);
defectSet.add(name);
}
}
}
assert defectSet.equals(expected)
: "defective path produced wrong set, size=" + defectSet.size()
+ " expected=" + expected.size();
System.out.printf("test1: E=%d S=%d local=%d expected unique names=%d — CORRECT%n",
E, S, localExports.length, expected.size());
}
// -----------------------------------------------------------------------
// Test 2 — ratio >= 5x at E=5, S=50 (medium barrel file)
// -----------------------------------------------------------------------
static void test2_ratioMediumBarrel() {
int E = 5, S = 50;
int[][] starModules = buildStarModules(E, S);
int[] localExports = {999_001, 999_002};
long defectOps = defectiveGetExportedNames(starModules, localExports);
long fixedOps = fixedGetExportedNames(starModules, localExports);
double ratio = (double) defectOps / Math.max(1, fixedOps);
System.out.printf("test2: E=%d S=%d defect=%d fixed=%d ratio=%.1fx%n",
E, S, defectOps, fixedOps, ratio);
assert ratio >= 5.0 : "expected ratio >= 5x at E=5,S=50, got " + ratio;
}
// -----------------------------------------------------------------------
// Test 3 — ratio >= 50x at E=10, S=100 (large barrel file scenario)
// -----------------------------------------------------------------------
static void test3_ratioLargeBarrel() {
int E = 10, S = 100;
int[][] starModules = buildStarModules(E, S);
int[] localExports = {};
long defectOps = defectiveGetExportedNames(starModules, localExports);
long fixedOps = fixedGetExportedNames(starModules, localExports);
double ratio = (double) defectOps / Math.max(1, fixedOps);
System.out.printf("test3: E=%d S=%d defect=%d fixed=%d ratio=%.1fx%n",
E, S, defectOps, fixedOps, ratio);
assert ratio >= 50.0 : "expected ratio >= 50x at E=10,S=100, got " + ratio;
}
// -----------------------------------------------------------------------
// Test 4 — secondary defect: GatherAvailableModuleAncestors
// ratio >= 5x with 100 async modules in a star topology
// -----------------------------------------------------------------------
static void test4_gatherAncestorsRatio() {
// Build a star topology: module 0 has 100 parents (1..100),
// each parent also has 10 more parents (creating deep gather)
int numModules = 150;
int[][] parentGraph = new int[numModules][];
parentGraph[0] = new int[100];
for (int i = 0; i < 100; i++) parentGraph[0][i] = i + 1;
for (int i = 1; i <= 100; i++) {
int parentStart = 100 + (i - 1) / 10;
if (parentStart < numModules) {
parentGraph[i] = new int[]{parentStart};
} else {
parentGraph[i] = new int[]{};
}
}
for (int i = 101; i < numModules; i++) {
parentGraph[i] = new int[]{};
}
long defectOps = defectiveGatherAncestors(parentGraph, 0);
long fixedOps = fixedGatherAncestors(parentGraph, 0);
double ratio = (double) defectOps / Math.max(1, fixedOps);
System.out.printf("test4: async graph %d modules defect=%d fixed=%d ratio=%.1fx%n",
numModules, defectOps, fixedOps, ratio);
assert ratio >= 5.0 : "expected ratio >= 5x for async gather, got " + ratio;
}
// -----------------------------------------------------------------------
// Test 5 — quadratic growth: doubling E roughly quadruples defect ops
// while fixed grows linearly
// -----------------------------------------------------------------------
static void test5_quadraticGrowth() {
int S = 80;
int[] localExports = {};
long d5 = defectiveGetExportedNames(buildStarModules(5, S), localExports);
long d10 = defectiveGetExportedNames(buildStarModules(10, S), localExports);
long f5 = fixedGetExportedNames(buildStarModules(5, S), localExports);
long f10 = fixedGetExportedNames(buildStarModules(10, S), localExports);
double defectGrowth = (double) d10 / Math.max(1, d5);
double fixedGrowth = (double) f10 / Math.max(1, f5);
System.out.printf("test5: S=%d E=5→10 defect %d→%d (%.2fx) fixed %d→%d (%.2fx)%n",
S, d5, d10, defectGrowth, f5, f10, fixedGrowth);
// Defect should grow super-linearly (quadratic) vs fixed (linear)
assert defectGrowth > fixedGrowth * 1.5
: "defect should grow faster than fixed when E doubles, "
+ "defectGrowth=" + defectGrowth + " fixedGrowth=" + fixedGrowth;
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== SpiderMonkeyModulesTest ===");
System.out.println("Modelling CWE-407 sm-0004: Modules.cpp ContainsElement(exportedNames) O(N) scan");
System.out.println("Location: js/src/vm/Modules.cpp ModuleGetExportedNames() + GatherAvailableModuleAncestors()");
System.out.println();
test1_correctness();
System.out.println(" PASS test1_correctness");
test2_ratioMediumBarrel();
System.out.println(" PASS test2_ratioMediumBarrel");
test3_ratioLargeBarrel();
System.out.println(" PASS test3_ratioLargeBarrel");
test4_gatherAncestorsRatio();
System.out.println(" PASS test4_gatherAncestorsRatio");
test5_quadraticGrowth();
System.out.println(" PASS test5_quadraticGrowth");
System.out.println();
System.out.println("5/5 PASS");
}
}