java-topology/defects/ruby/unit/RubyGemsDependentGemsAlgorithm.java
2026-03-29 22:19:47 -04:00

153 lines
5.6 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.*;
/**
* 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<String> depNames; // simplified: just dep gem names
GemSpec(String name, String version, List<String> 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<GemSpec> findAllSatisfiers(List<GemSpec> allSpecs, String depName) {
List<GemSpec> 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<Object[]> dependentGems(GemSpec self, List<GemSpec> allSpecs) {
List<Object[]> 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<GemSpec> 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<Object[]> dependentGems(GemSpec self, List<GemSpec> allSpecs) {
// Build reverse index: name -> [specs]
Map<String, List<GemSpec>> nameIndex = new HashMap<>();
for (GemSpec s : allSpecs) {
nameIndex.computeIfAbsent(s.name, k -> new ArrayList<>()).add(s);
ops++;
}
List<Object[]> 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<GemSpec> sats = nameIndex.getOrDefault(depName, Collections.emptyList());
out.add(new Object[]{spec, depName, sats});
}
}
}
return out;
}
}
static List<GemSpec> makeSpecs(int n, int depsPerSpec) {
List<String> names = new ArrayList<>();
for (int i = 0; i < n; i++) names.add("gem_" + i);
List<GemSpec> specs = new ArrayList<>();
Random rng = new Random(42);
for (int i = 0; i < n; i++) {
List<String> 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<GemSpec> 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<Object[]> slowResult = slow.dependentGems(target, allSpecs);
List<Object[]> 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);
}
}