151 lines
5.4 KiB
Java
151 lines
5.4 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* Models Bazel FeatureSelection.run() provides-conflict check.
|
||
*
|
||
* SLOW: O(P×S×L) — ImmutableList.contains() O(L) per selectable in P×S nested loops.
|
||
* FAST: O(P×S) — HashSet built once from enabledList; O(1) per lookup.
|
||
*
|
||
* CWE-407: src/main/java/com/google/devtools/build/lib/rules/cpp/FeatureSelection.java:159
|
||
*/
|
||
public class BazelFeatureSelectionAlgorithmTest {
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Slow — ImmutableList.contains() O(L) per lookup
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class SlowFeatureSelection {
|
||
long cmpOps = 0;
|
||
|
||
boolean listContains(List<String> list, String val) {
|
||
for (String s : list) {
|
||
cmpOps++;
|
||
if (s.equals(val)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* O(P × S × L): for each provide × selectable, check enabledList membership.
|
||
* Returns count of conflict checks performed.
|
||
*/
|
||
int run(List<String> enabledList,
|
||
Map<String, List<String>> providesMap) {
|
||
int conflicts = 0;
|
||
for (Map.Entry<String, List<String>> entry : providesMap.entrySet()) {
|
||
for (String selectable : entry.getValue()) {
|
||
if (listContains(enabledList, selectable)) {
|
||
conflicts++;
|
||
}
|
||
}
|
||
}
|
||
return conflicts;
|
||
}
|
||
|
||
long ops(List<String> enabledList, Map<String, List<String>> providesMap) {
|
||
cmpOps = 0;
|
||
run(enabledList, providesMap);
|
||
return cmpOps;
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Fast — HashSet built once before loop
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class FastFeatureSelection {
|
||
long cmpOps = 0;
|
||
|
||
int run(List<String> enabledList,
|
||
Map<String, List<String>> providesMap) {
|
||
Set<String> enabledSet = new HashSet<>(enabledList);
|
||
cmpOps += enabledList.size(); // build cost
|
||
int conflicts = 0;
|
||
for (Map.Entry<String, List<String>> entry : providesMap.entrySet()) {
|
||
for (String selectable : entry.getValue()) {
|
||
cmpOps++;
|
||
if (enabledSet.contains(selectable)) {
|
||
conflicts++;
|
||
}
|
||
}
|
||
}
|
||
return conflicts;
|
||
}
|
||
|
||
long ops(List<String> enabledList, Map<String, List<String>> providesMap) {
|
||
cmpOps = 0;
|
||
run(enabledList, providesMap);
|
||
return cmpOps;
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Build test data
|
||
// -------------------------------------------------------------------------
|
||
|
||
static List<String> makeEnabledList(int l) {
|
||
List<String> list = new ArrayList<>();
|
||
for (int i = 0; i < l; i++) list.add("feature_" + i);
|
||
return list;
|
||
}
|
||
|
||
static Map<String, List<String>> makeProvidesMap(int p, int s, int overlapStart) {
|
||
Map<String, List<String>> map = new LinkedHashMap<>();
|
||
for (int i = 0; i < p; i++) {
|
||
List<String> sels = new ArrayList<>();
|
||
for (int j = 0; j < s; j++) {
|
||
// Some selectables are in enabledList (conflicts), some not
|
||
sels.add("feature_" + (overlapStart + i * s + j));
|
||
}
|
||
map.put("provide_" + i, sels);
|
||
}
|
||
return map;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
SlowFeatureSelection slow = new SlowFeatureSelection();
|
||
FastFeatureSelection fast = new FastFeatureSelection();
|
||
|
||
int passed = 0, total = 0;
|
||
|
||
System.out.println("=== bazel-0003: FeatureSelection ImmutableList.contains O(P×S×L) ===");
|
||
|
||
// (L, P, S) configurations matching realistic Bazel C++ toolchain sizes
|
||
int[][] configs = {
|
||
{50, 7, 6}, // small toolchain
|
||
{80, 7, 6}, // realistic (agent's estimate)
|
||
{150, 10, 8}, // large toolchain
|
||
{300, 15, 10} // extreme
|
||
};
|
||
for (int[] cfg : configs) {
|
||
int l = cfg[0], p = cfg[1], s = cfg[2];
|
||
List<String> enabled = makeEnabledList(l);
|
||
Map<String, List<String>> provides = makeProvidesMap(p, s, 0);
|
||
|
||
long sv = slow.ops(enabled, provides);
|
||
long fv = fast.ops(enabled, provides);
|
||
double ratio = (double) sv / Math.max(fv, 1);
|
||
|
||
total++;
|
||
boolean ok = sv > fv && ratio >= 5.0;
|
||
System.out.printf("L=%3d P=%2d S=%2d slow=%7d fast=%5d ratio=%6.1fx %s%n",
|
||
l, p, s, sv, fv, ratio, ok ? "PASS" : "FAIL");
|
||
if (ok) passed++;
|
||
}
|
||
|
||
// Correctness: both count same conflicts
|
||
List<String> enabled = makeEnabledList(80);
|
||
Map<String, List<String>> provides = makeProvidesMap(7, 6, 0);
|
||
int sc = slow.run(enabled, provides);
|
||
int fc = fast.run(enabled, provides);
|
||
total++;
|
||
boolean correct = sc == fc;
|
||
System.out.printf("conflict count matches (%d): %s%n", sc, correct ? "PASS" : "FAIL");
|
||
if (correct) passed++;
|
||
|
||
System.out.printf("%n%d/%d PASS%n", passed, total);
|
||
if (passed < total) System.exit(1);
|
||
}
|
||
}
|