153 lines
5.6 KiB
Java
153 lines
5.6 KiB
Java
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);
|
||
}
|
||
}
|