2 KiB
2 KiB
UNDF: UNDF-2026-000000581
bazel-0003: FeatureSelection ImmutableList.contains O(P×S×L) → O(P×S) with HashSet
Classification
| Field | Value |
|---|---|
| CWE | CWE-407 Inefficient Algorithmic Complexity |
| Severity | MEDIUM |
| Component | src/main/java/com/google/devtools/build/lib/rules/cpp/FeatureSelection.java:159 |
| Function | FeatureSelection.run() — provides-conflict check |
| Hot path | Once per action type per C++ target in computeFeatureConfiguration() |
| Status | PATCHED (unit test PASS) |
Defect
FeatureSelection.run() iterates over provides.keys() (P unique provide-strings) and
for each iterates provides.get(provided) (S selectables), calling
enabledActivatablesInOrder.contains(...) — an O(L) scan of an ImmutableList:
// FeatureSelection.java:159
ImmutableList<CrosstoolSelectable> enabledActivatablesInOrder = ...; // L entries
for (String provided : provides.keys()) { // P provide-strings
for (CrosstoolSelectable selectable : provides.get(provided)) { // S selectables
if (enabledActivatablesInOrder.contains( // O(L) linear scan
selectableProvidingString)) {
...
}
}
}
ImmutableList.contains() is O(L) — walks every element. With P=7, S=6, L=80:
~3,360 comparisons per call vs ~42 with a HashSet (80×).
computeFeatureConfiguration() is called once per action type per target in C++ builds.
Large monorepo builds with thousands of C++ targets hit this path repeatedly.
Fix
Build a HashSet<CrosstoolSelectable> from enabledActivatablesInOrder once before the
outer loop:
Set<CrosstoolSelectable> enabledSet = new HashSet<>(enabledActivatablesInOrder);
for (String provided : provides.keys()) {
for (CrosstoolSelectable selectable : provides.get(provided)) {
if (enabledSet.contains(selectableProvidingString)) { // O(1)
...
}
}
}
Speedup: ~80× at realistic scale (L=80, P=7, S=6).