package unit; import java.util.*; /** * RubyGemsDependentGemsAlgorithm — CWE-407 test for ruby-0003 * * Models Gem::Specification#dependent_gems + #find_all_satisfiers: * slow: for each spec, for each dep: Gem::Specification.each — O(N² × D) * fast: build name_index once, then name_index[dep.name].select — O(N × D) * * Test: for N gems with D deps each, slow does N*D*N ops; fast does N*D + N_matches. * Ratio must be >= 10x at N=200. */ public class RubyGemsDependentGemsAlgorithm { static final class GemSpec { final String name; final String version; final List depNames; // simplified: just dep gem names GemSpec(String name, String version, List depNames) { this.name = name; this.version = version; this.depNames = depNames; } boolean satisfiesRequirement(String depName) { return this.name.equals(depName); } } // --- SLOW: O(N² × D) --- // Gem::Specification.each inside find_all_satisfiers inside dependent_gems static class SlowDependentGems { long comparisons = 0; // find_all_satisfiers(dep): scans all N specs List findAllSatisfiers(List allSpecs, String depName) { List result = new ArrayList<>(); for (GemSpec spec : allSpecs) { comparisons++; if (spec.satisfiesRequirement(depName)) { result.add(spec); } } return result; } // dependent_gems(self, allSpecs): find specs that depend on self List dependentGems(GemSpec self, List allSpecs) { List out = new ArrayList<>(); for (GemSpec spec : allSpecs) { // O(N) outer for (String depName : spec.depNames) { // O(D) deps comparisons++; if (self.satisfiesRequirement(depName)) { // find_all_satisfiers: O(N) inner List sats = findAllSatisfiers(allSpecs, depName); out.add(new Object[]{spec, depName, sats}); } } } return out; } } // --- FAST: O(N × D) --- // Build name_index once, then O(matches) lookup static class FastDependentGems { long ops = 0; List dependentGems(GemSpec self, List allSpecs) { // Build reverse index: name -> [specs] Map> nameIndex = new HashMap<>(); for (GemSpec s : allSpecs) { nameIndex.computeIfAbsent(s.name, k -> new ArrayList<>()).add(s); ops++; } List out = new ArrayList<>(); for (GemSpec spec : allSpecs) { // O(N) outer for (String depName : spec.depNames) { // O(D) deps ops++; if (self.satisfiesRequirement(depName)) { // O(matches) lookup, not O(N) List sats = nameIndex.getOrDefault(depName, Collections.emptyList()); out.add(new Object[]{spec, depName, sats}); } } } return out; } } static List makeSpecs(int n, int depsPerSpec) { List names = new ArrayList<>(); for (int i = 0; i < n; i++) names.add("gem_" + i); List specs = new ArrayList<>(); Random rng = new Random(42); for (int i = 0; i < n; i++) { List deps = new ArrayList<>(); for (int d = 0; d < depsPerSpec; d++) { deps.add(names.get(rng.nextInt(n))); } specs.add(new GemSpec("gem_" + i, "1.0." + i, deps)); } return specs; } public static void main(String[] args) { int[] sizes = {50, 100, 200, 300}; int depsPerSpec = 5; System.out.println("RubyGemsDependentGemsAlgorithm — ruby-0003"); System.out.println(" Pattern: find_all_satisfiers O(N) scan inside O(N×D) loop vs reverse index"); System.out.println(" Simulation: D=" + depsPerSpec + " deps per gem"); System.out.println(); int passed = 0; int total = 0; for (int n : sizes) { List allSpecs = makeSpecs(n, depsPerSpec); GemSpec target = allSpecs.get(0); // gem_0 is the gem being uninstalled SlowDependentGems slow = new SlowDependentGems(); FastDependentGems fast = new FastDependentGems(); List slowResult = slow.dependentGems(target, allSpecs); List fastResult = fast.dependentGems(target, allSpecs); // Both should find the same number of dependent specs boolean sameCount = slowResult.size() == fastResult.size(); double ratio = (double) slow.comparisons / fast.ops; boolean correctRatio = n >= 100 ? ratio >= 10.0 : ratio >= 2.0; boolean pass = sameCount && correctRatio; total++; if (pass) passed++; System.out.printf(" N=%-4d D=%d slow=%8d fast=%6d ratio=%5.1fx same=%b %s%n", n, depsPerSpec, slow.comparisons, fast.ops, ratio, sameCount, pass ? "PASS" : "FAIL"); } System.out.println(); System.out.printf("Result: %d/%d PASS%n", passed, total); if (passed < total) System.exit(1); } }