/* * Copyright (C) 2018 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.internal.codegen.bindinggraphvalidation; import static dagger.internal.codegen.base.Formatter.INDENT; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; import static dagger.internal.codegen.model.BindingKind.INJECTION; import static dagger.internal.codegen.model.BindingKind.MEMBERS_INJECTION; import static java.util.Comparator.comparing; import static javax.tools.Diagnostic.Kind.ERROR; import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import dagger.internal.codegen.base.Formatter; import dagger.internal.codegen.binding.BindingDeclaration; import dagger.internal.codegen.binding.BindingDeclarationFormatter; import dagger.internal.codegen.binding.BindingNode; import dagger.internal.codegen.binding.MultibindingDeclaration; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.model.Binding; import dagger.internal.codegen.model.BindingGraph; import dagger.internal.codegen.model.BindingGraph.ComponentNode; import dagger.internal.codegen.model.BindingKind; import dagger.internal.codegen.model.ComponentPath; import dagger.internal.codegen.model.DaggerAnnotation; import dagger.internal.codegen.model.DaggerElement; import dagger.internal.codegen.model.DaggerTypeElement; import dagger.internal.codegen.model.DiagnosticReporter; import dagger.internal.codegen.model.Key; import dagger.internal.codegen.model.Key.MultibindingContributionIdentifier; import dagger.internal.codegen.validation.ValidationBindingGraphPlugin; import dagger.internal.codegen.xprocessing.XTypes; import java.util.Comparator; import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import javax.inject.Inject; import javax.tools.Diagnostic; /** Reports errors for conflicting bindings with the same key. */ final class DuplicateBindingsValidator extends ValidationBindingGraphPlugin { private static final Comparator BY_LENGTH_OF_COMPONENT_PATH = comparing(binding -> binding.componentPath().components().size()); private final BindingDeclarationFormatter bindingDeclarationFormatter; private final CompilerOptions compilerOptions; @Inject DuplicateBindingsValidator( BindingDeclarationFormatter bindingDeclarationFormatter, CompilerOptions compilerOptions) { this.bindingDeclarationFormatter = bindingDeclarationFormatter; this.compilerOptions = compilerOptions; } @Override public String pluginName() { return "Dagger/DuplicateBindings"; } @Override public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { // If two unrelated subcomponents have the same duplicate bindings only because they install the // same two modules, then fixing the error in one subcomponent will uncover the second // subcomponent to fix. // TODO(ronshapiro): Explore ways to address such underreporting without overreporting. Set> reportedDuplicateBindingSets = new HashSet<>(); duplicateBindingSets(bindingGraph) .forEach( duplicateBindings -> { // Only report each set of duplicate bindings once, ignoring the installed component. if (reportedDuplicateBindingSets.add(duplicateBindings.keySet())) { reportErrors(duplicateBindings, bindingGraph, diagnosticReporter); } }); } /** * Returns sets of duplicate bindings. Bindings are duplicates if they bind the same key and are * visible from the same component. Two bindings that differ only in the component that owns them * are not considered to be duplicates, because that means the same binding was "copied" down to a * descendant component because it depends on local multibindings or optional bindings. Hence each * "set" is represented as a multimap from binding element (ignoring component path) to binding. */ private ImmutableSet> duplicateBindingSets( BindingGraph bindingGraph) { return groupBindingsByKey(bindingGraph).stream() .flatMap(bindings -> mutuallyVisibleSubsets(bindings).stream()) .map(BindingWithoutComponent::index) .filter(duplicates -> duplicates.keySet().size() > 1) .collect(toImmutableSet()); } private ImmutableSet> groupBindingsByKey(BindingGraph bindingGraph) { return valueSetsForEachKey( bindingGraph.bindings().stream() .filter(binding -> !binding.kind().equals(MEMBERS_INJECTION)) .collect( toImmutableSetMultimap( binding -> // If the "ignoreProvisionKeyWildcards" flag is enabled then ignore the // variance in the key types here so that Foo and Foo // get grouped into the same set (i.e. as duplicates). KeyWithTypeEquivalence.forKey( binding.key(), compilerOptions.ignoreProvisionKeyWildcards() ? XTypes.equivalenceIgnoringVariance() : XTypes.equivalence()), binding -> binding))); } /** * Returns the subsets of the input set that contain bindings that are all visible from the same * component. A binding is visible from its component and all its descendants. */ private static ImmutableSet> mutuallyVisibleSubsets( Set duplicateBindings) { ImmutableListMultimap bindingsByComponentPath = Multimaps.index(duplicateBindings, Binding::componentPath); ImmutableSetMultimap.Builder mutuallyVisibleBindings = ImmutableSetMultimap.builder(); bindingsByComponentPath .asMap() .forEach( (componentPath, bindings) -> { mutuallyVisibleBindings.putAll(componentPath, bindings); for (ComponentPath ancestor = componentPath; !ancestor.atRoot(); ) { ancestor = ancestor.parent(); ImmutableList bindingsInAncestor = bindingsByComponentPath.get(ancestor); mutuallyVisibleBindings.putAll(componentPath, bindingsInAncestor); } }); return valueSetsForEachKey(mutuallyVisibleBindings.build()); } private void reportErrors( ImmutableSetMultimap duplicateBindings, BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { if (explicitBindingConfictsWithInject(duplicateBindings.keySet())) { compilerOptions .explicitBindingConflictsWithInjectValidationType() .diagnosticKind() .ifPresent( diagnosticKind -> reportExplicitBindingConflictsWithInject( duplicateBindings.values(), diagnosticReporter, diagnosticKind, bindingGraph.rootComponentNode())); return; } reportDuplicateBindings(duplicateBindings.values(), bindingGraph, diagnosticReporter); } /** * Returns {@code true} if the bindings contain one {@code @Inject} binding and one that isn't. */ private static boolean explicitBindingConfictsWithInject( ImmutableSet duplicateBindings) { ImmutableMultiset bindingKinds = Multimaps.index(duplicateBindings, BindingWithoutComponent::bindingKind).keys(); return bindingKinds.count(INJECTION) == 1 && bindingKinds.size() == 2; } private void reportExplicitBindingConflictsWithInject( ImmutableCollection duplicateBindings, DiagnosticReporter diagnosticReporter, Diagnostic.Kind diagnosticKind, ComponentNode rootComponent) { Binding injectBinding = rootmostBindingWithKind(k -> k.equals(INJECTION), duplicateBindings); Binding explicitBinding = rootmostBindingWithKind(k -> !k.equals(INJECTION), duplicateBindings); StringBuilder message = new StringBuilder() .append(explicitBinding.key()) .append(" is bound multiple times:") .append(formatWithComponentPath(injectBinding)) .append(formatWithComponentPath(explicitBinding)) .append( "\nThis condition was never validated before, and will soon be an error. " + "See https://dagger.dev/conflicting-inject."); if (compilerOptions.experimentalDaggerErrorMessages()) { diagnosticReporter.reportComponent(diagnosticKind, rootComponent, message.toString()); } else { diagnosticReporter.reportBinding(diagnosticKind, explicitBinding, message.toString()); } } private String formatWithComponentPath(Binding binding) { return String.format( "\n%s%s [%s]", Formatter.INDENT, bindingDeclarationFormatter.format(((BindingNode) binding).delegate()), binding.componentPath()); } private void reportDuplicateBindings( ImmutableCollection duplicateBindings, BindingGraph graph, DiagnosticReporter diagnosticReporter) { StringBuilder message = new StringBuilder(); Binding oneBinding = duplicateBindings.asList().get(0); ImmutableSet multibindings = duplicateBindings.stream() .filter(binding -> binding.kind().isMultibinding()) .collect(toImmutableSet()); if (multibindings.isEmpty()) { message.append(oneBinding.key()).append(" is bound multiple times:"); formatDeclarations(message, 2, declarations(graph, duplicateBindings)); } else { Binding oneMultibinding = multibindings.asList().get(0); message.append(oneMultibinding.key()).append(" has incompatible bindings or declarations:\n"); message .append(INDENT) .append(multibindingTypeString(oneMultibinding)) .append(" bindings and declarations:"); formatDeclarations(message, 2, declarations(graph, multibindings)); ImmutableSet uniqueBindingDeclarations = duplicateBindings.stream() .filter(binding -> !binding.kind().isMultibinding()) .flatMap(binding -> declarations(graph, binding).stream()) .filter(declaration -> !(declaration instanceof MultibindingDeclaration)) .collect(toImmutableSet()); if (!uniqueBindingDeclarations.isEmpty()) { message.append('\n').append(INDENT).append("Unique bindings and declarations:"); formatDeclarations(message, 2, uniqueBindingDeclarations); } } if (compilerOptions.experimentalDaggerErrorMessages()) { message.append(String.format("\n%sin component: [%s]", INDENT, oneBinding.componentPath())); diagnosticReporter.reportComponent(ERROR, graph.rootComponentNode(), message.toString()); } else { diagnosticReporter.reportBinding(ERROR, oneBinding, message.toString()); } } private void formatDeclarations( StringBuilder builder, int indentLevel, Iterable bindingDeclarations) { bindingDeclarationFormatter.formatIndentedList( builder, ImmutableList.copyOf(bindingDeclarations), indentLevel); } private ImmutableSet declarations( BindingGraph graph, ImmutableCollection bindings) { return bindings.stream() .flatMap(binding -> declarations(graph, binding).stream()) .distinct() .sorted(BindingDeclaration.COMPARATOR) .collect(toImmutableSet()); } private ImmutableSet declarations(BindingGraph graph, Binding binding) { ImmutableSet.Builder declarations = ImmutableSet.builder(); BindingNode bindingNode = (BindingNode) binding; bindingNode.associatedDeclarations().forEach(declarations::add); if (bindingDeclarationFormatter.canFormat(bindingNode.delegate())) { declarations.add(bindingNode.delegate()); } else { graph.requestedBindings(binding).stream() .flatMap(requestedBinding -> declarations(graph, requestedBinding).stream()) .forEach(declarations::add); } return declarations.build(); } private String multibindingTypeString(Binding multibinding) { switch (multibinding.kind()) { case MULTIBOUND_MAP: return "Map"; case MULTIBOUND_SET: return "Set"; default: throw new AssertionError(multibinding); } } private static ImmutableSet> valueSetsForEachKey(Multimap multimap) { return multimap.asMap().values().stream().map(ImmutableSet::copyOf).collect(toImmutableSet()); } /** Returns the binding of the given kind that is closest to the root component. */ private static Binding rootmostBindingWithKind( Predicate bindingKindPredicate, ImmutableCollection bindings) { return bindings.stream() .filter(b -> bindingKindPredicate.test(b.kind())) .min(BY_LENGTH_OF_COMPONENT_PATH) .get(); } /** The identifying information about a binding, excluding its {@link Binding#componentPath()}. */ @AutoValue abstract static class BindingWithoutComponent { abstract BindingKind bindingKind(); abstract Key bindingKey(); abstract Optional bindingElement(); abstract Optional contributingModule(); static ImmutableSetMultimap index(Set bindings) { return bindings.stream() .collect(toImmutableSetMultimap(BindingWithoutComponent::forBinding, b -> b)); } private static BindingWithoutComponent forBinding(Binding binding) { return new AutoValue_DuplicateBindingsValidator_BindingWithoutComponent( binding.kind(), binding.key(), binding.bindingElement().map(DaggerElement::xprocessing), binding.contributingModule().map(DaggerTypeElement::xprocessing)); } } /** The identifying information about a key with the given type equivalence. */ @AutoValue abstract static class KeyWithTypeEquivalence { abstract Optional qualifier(); abstract Equivalence.Wrapper wrappedType(); abstract Optional multibindingContributionIdentifier(); private static KeyWithTypeEquivalence forKey(Key key, Equivalence typeEquivalence) { return new AutoValue_DuplicateBindingsValidator_KeyWithTypeEquivalence( key.qualifier(), typeEquivalence.wrap(key.type().xprocessing()), key.multibindingContributionIdentifier()); } } }