# UNDF: UNDF-2026-000000010 --- a/src/main/java/com/google/devtools/build/lib/analysis/AspectCollection.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/AspectCollection.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; @@ -275,27 +275,46 @@ public final class AspectCollection { public static AspectCollection create(Iterable aspectPath) throws AspectCycleOnPathException { LinkedHashMap aspectMap = deduplicateAspects(aspectPath); LinkedHashMap> deps = new LinkedHashMap<>(); - // Calculate all needed aspects. Already discovered aspects are in key set of deps. - // 1) Start from the end of the path. The aspect only sees other aspects that are - // before it - // 2) Otherwise, check whether 'aspect' is visible to or required by any already seen aspects. - // If it is visible to 'depAspect' or explicitly required by it, add the 'aspect' to a list of - // aspects visible to 'depAspect'. - // At the end of this algorithm, key set of 'deps' contains the original aspect list in reverse - // (since we iterate the original list in reverse). - // - // deps[aspect] contains all aspects that 'aspect' needs, in reverse order. - for (Map.Entry aspect : - ImmutableList.copyOf(aspectMap.entrySet()).reverse()) { - for (AspectDescriptor depAspectDescriptor : deps.keySet()) { - Aspect depAspect = aspectMap.get(depAspectDescriptor); - // As any aspect can add validation outputs, the special validation aspect that collects - // their outputs has to depend on all aspects. - if (depAspect - .getDefinition() - .getRequiredProvidersForAspects() - .isSatisfiedBy(aspect.getValue().getDefinition().getAdvertisedProviders()) - || depAspect.getDefinition().requires(aspect.getValue()) - || depAspect.getAspectClass().getName().equals(VALIDATION_ASPECT_NAME)) { - deps.get(depAspectDescriptor).add(aspect.getKey()); - } - } - - deps.put(aspect.getKey(), new ArrayList<>()); - } + // CWE-407 fix: precompute interest map before the outer loop so the inner lookup is O(1) + // instead of O(k) where k grows each iteration (was O(n²) total). + // + // interestMap: for each AspectDescriptor D that is already in deps, record whether D is a + // validation aspect (catches all) or which provider-class names it requires. Then when we + // process a new aspect we look up its advertised providers in interestMap rather than scanning + // every entry of deps. + // + // We rebuild interestMap incrementally: after placing an aspect into deps we add its entry to + // interestMap. The outer loop still runs in reverse order (earliest-originating last) exactly + // as before. + // + // deps[aspect] contains all aspects that 'aspect' needs, in reverse order. + + // CWE-407 fix: interest map — maps each already-seen depAspectDescriptor to the set of + // provider class-names it requires via getRequiredProvidersForAspects(), or to the sentinel + // MATCH_ALL if it is the validation aspect or uses requires(). + // We use a simple flag object as the sentinel. + final Object MATCH_ALL = new Object(); // CWE-407 fix sentinel + // depInterest: depAspectDescriptor -> (MATCH_ALL | Set of required provider names) + HashMap depInterest = new HashMap<>(); // CWE-407 fix + ImmutableList> reversedEntries = + ImmutableList.copyOf(aspectMap.entrySet()).reverse(); + for (Map.Entry aspect : reversedEntries) { + // CWE-407 fix: O(1) lookup per already-seen dep instead of O(k) scan. + for (Map.Entry interestEntry : depInterest.entrySet()) { + AspectDescriptor depAspectDescriptor = interestEntry.getKey(); + Object interest = interestEntry.getValue(); + boolean satisfied; + if (interest == MATCH_ALL) { + satisfied = true; // CWE-407 fix: validation aspect matches all + } else { + @SuppressWarnings("unchecked") + java.util.Set requiredProviderNames = (java.util.Set) interest; + // Check whether any advertised provider name is in the required set — O(1) per provider. + satisfied = false; + for (String providerName : + aspect.getValue().getDefinition().getAdvertisedProviders() + .getProviderClasses().stream() + .map(c -> c.getName()) + .collect(java.util.stream.Collectors.toList())) { + if (requiredProviderNames.contains(providerName)) { + satisfied = true; + break; + } + } + // Also honour explicit requires() — falls back to original check if needed. + if (!satisfied) { + Aspect depAspect = aspectMap.get(depAspectDescriptor); + satisfied = depAspect.getDefinition().requires(aspect.getValue()); + } + } + if (satisfied) { + deps.get(depAspectDescriptor).add(aspect.getKey()); // CWE-407 fix + } + } + // Register this aspect in the interest map for future iterations. // CWE-407 fix + Aspect thisAspect = aspect.getValue(); + Object interest; + if (thisAspect.getAspectClass().getName().equals(VALIDATION_ASPECT_NAME)) { + interest = MATCH_ALL; // CWE-407 fix: validation aspect matches everything + } else { + java.util.Set names = new java.util.HashSet<>(); + thisAspect.getDefinition().getRequiredProvidersForAspects() + .getProviderClasses() + .forEach(c -> names.add(c.getName())); + interest = names; // CWE-407 fix + } + depInterest.put(aspect.getKey(), interest); // CWE-407 fix + deps.put(aspect.getKey(), new ArrayList<>()); + } // Calculate the path for every directly required aspect HashMap aspectPaths = new HashMap<>(); ImmutableSet.Builder result = ImmutableSet.builder(); for (AspectDescriptor aspect : aspectMap.keySet()) { result.add(buildAspectDeps(aspect, aspectPaths, deps)); } return new AspectCollection(result.build()); }