339 lines
13 KiB
Java
339 lines
13 KiB
Java
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");
|
||
}
|
||
}
|