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 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 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 exportedNames = new ArrayList<>(); HashSet 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 execList = new ArrayList<>(); long comparisons = 0; // BFS using stack to simulate recursive gather ArrayList 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 execSet = new HashSet<>(); ArrayList 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 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 defectResult = new ArrayList<>(); HashSet 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"); } }