1 /* 2 * Copyright (C) 2018 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen.bindinggraphvalidation; 18 19 import static dagger.internal.codegen.base.Formatter.INDENT; 20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; 22 import static dagger.internal.codegen.model.BindingKind.INJECTION; 23 import static dagger.internal.codegen.model.BindingKind.MEMBERS_INJECTION; 24 import static java.util.Comparator.comparing; 25 import static javax.tools.Diagnostic.Kind.ERROR; 26 27 import androidx.room.compiler.processing.XElement; 28 import androidx.room.compiler.processing.XType; 29 import androidx.room.compiler.processing.XTypeElement; 30 import com.google.auto.value.AutoValue; 31 import com.google.common.base.Equivalence; 32 import com.google.common.collect.ImmutableCollection; 33 import com.google.common.collect.ImmutableList; 34 import com.google.common.collect.ImmutableListMultimap; 35 import com.google.common.collect.ImmutableMultiset; 36 import com.google.common.collect.ImmutableSet; 37 import com.google.common.collect.ImmutableSetMultimap; 38 import com.google.common.collect.Multimap; 39 import com.google.common.collect.Multimaps; 40 import dagger.internal.codegen.base.Formatter; 41 import dagger.internal.codegen.binding.BindingNode; 42 import dagger.internal.codegen.binding.Declaration; 43 import dagger.internal.codegen.binding.DeclarationFormatter; 44 import dagger.internal.codegen.binding.MultibindingDeclaration; 45 import dagger.internal.codegen.compileroption.CompilerOptions; 46 import dagger.internal.codegen.model.Binding; 47 import dagger.internal.codegen.model.BindingGraph; 48 import dagger.internal.codegen.model.BindingGraph.ComponentNode; 49 import dagger.internal.codegen.model.BindingKind; 50 import dagger.internal.codegen.model.ComponentPath; 51 import dagger.internal.codegen.model.DaggerAnnotation; 52 import dagger.internal.codegen.model.DaggerElement; 53 import dagger.internal.codegen.model.DaggerTypeElement; 54 import dagger.internal.codegen.model.DiagnosticReporter; 55 import dagger.internal.codegen.model.Key; 56 import dagger.internal.codegen.model.Key.MultibindingContributionIdentifier; 57 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin; 58 import dagger.internal.codegen.xprocessing.XTypes; 59 import java.util.Comparator; 60 import java.util.HashSet; 61 import java.util.Optional; 62 import java.util.Set; 63 import java.util.function.Predicate; 64 import javax.inject.Inject; 65 import javax.tools.Diagnostic; 66 67 /** Reports errors for conflicting bindings with the same key. */ 68 final class DuplicateBindingsValidator extends ValidationBindingGraphPlugin { 69 70 private static final Comparator<Binding> BY_LENGTH_OF_COMPONENT_PATH = 71 comparing(binding -> binding.componentPath().components().size()); 72 73 private final DeclarationFormatter declarationFormatter; 74 private final CompilerOptions compilerOptions; 75 76 @Inject DuplicateBindingsValidator( DeclarationFormatter declarationFormatter, CompilerOptions compilerOptions)77 DuplicateBindingsValidator( 78 DeclarationFormatter declarationFormatter, CompilerOptions compilerOptions) { 79 this.declarationFormatter = declarationFormatter; 80 this.compilerOptions = compilerOptions; 81 } 82 83 @Override pluginName()84 public String pluginName() { 85 return "Dagger/DuplicateBindings"; 86 } 87 88 @Override visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)89 public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { 90 // If two unrelated subcomponents have the same duplicate bindings only because they install the 91 // same two modules, then fixing the error in one subcomponent will uncover the second 92 // subcomponent to fix. 93 // TODO(ronshapiro): Explore ways to address such underreporting without overreporting. 94 Set<ImmutableSet<BindingWithoutComponent>> reportedDuplicateBindingSets = new HashSet<>(); 95 duplicateBindingSets(bindingGraph) 96 .forEach( 97 duplicateBindings -> { 98 // Only report each set of duplicate bindings once, ignoring the installed component. 99 if (reportedDuplicateBindingSets.add(duplicateBindings.keySet())) { 100 reportErrors(duplicateBindings, bindingGraph, diagnosticReporter); 101 } 102 }); 103 } 104 105 /** 106 * Returns sets of duplicate bindings. Bindings are duplicates if they bind the same key and are 107 * visible from the same component. Two bindings that differ only in the component that owns them 108 * are not considered to be duplicates, because that means the same binding was "copied" down to a 109 * descendant component because it depends on local multibindings or optional bindings. Hence each 110 * "set" is represented as a multimap from binding element (ignoring component path) to binding. 111 */ duplicateBindingSets( BindingGraph bindingGraph)112 private ImmutableSet<ImmutableSetMultimap<BindingWithoutComponent, Binding>> duplicateBindingSets( 113 BindingGraph bindingGraph) { 114 return groupBindingsByKey(bindingGraph).stream() 115 .flatMap(bindings -> mutuallyVisibleSubsets(bindings).stream()) 116 .map(BindingWithoutComponent::index) 117 .filter(duplicates -> duplicates.keySet().size() > 1) 118 .collect(toImmutableSet()); 119 } 120 groupBindingsByKey(BindingGraph bindingGraph)121 private ImmutableSet<ImmutableSet<Binding>> groupBindingsByKey(BindingGraph bindingGraph) { 122 return valueSetsForEachKey( 123 bindingGraph.bindings().stream() 124 .filter(binding -> !binding.kind().equals(MEMBERS_INJECTION)) 125 .collect( 126 toImmutableSetMultimap( 127 binding -> 128 // If the "ignoreProvisionKeyWildcards" flag is enabled then ignore the 129 // variance in the key types here so that Foo<Bar> and Foo<? extends Bar> 130 // get grouped into the same set (i.e. as duplicates). 131 KeyWithTypeEquivalence.forKey( 132 binding.key(), 133 compilerOptions.ignoreProvisionKeyWildcards() 134 ? XTypes.equivalenceIgnoringVariance() 135 : XTypes.equivalence()), 136 binding -> binding))); 137 } 138 139 /** 140 * Returns the subsets of the input set that contain bindings that are all visible from the same 141 * component. A binding is visible from its component and all its descendants. 142 */ mutuallyVisibleSubsets( Set<Binding> duplicateBindings)143 private static ImmutableSet<ImmutableSet<Binding>> mutuallyVisibleSubsets( 144 Set<Binding> duplicateBindings) { 145 ImmutableListMultimap<ComponentPath, Binding> bindingsByComponentPath = 146 Multimaps.index(duplicateBindings, Binding::componentPath); 147 ImmutableSetMultimap.Builder<ComponentPath, Binding> mutuallyVisibleBindings = 148 ImmutableSetMultimap.builder(); 149 bindingsByComponentPath 150 .asMap() 151 .forEach( 152 (componentPath, bindings) -> { 153 mutuallyVisibleBindings.putAll(componentPath, bindings); 154 for (ComponentPath ancestor = componentPath; !ancestor.atRoot(); ) { 155 ancestor = ancestor.parent(); 156 ImmutableList<Binding> bindingsInAncestor = bindingsByComponentPath.get(ancestor); 157 mutuallyVisibleBindings.putAll(componentPath, bindingsInAncestor); 158 } 159 }); 160 return valueSetsForEachKey(mutuallyVisibleBindings.build()); 161 } 162 reportErrors( ImmutableSetMultimap<BindingWithoutComponent, Binding> duplicateBindings, BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)163 private void reportErrors( 164 ImmutableSetMultimap<BindingWithoutComponent, Binding> duplicateBindings, 165 BindingGraph bindingGraph, 166 DiagnosticReporter diagnosticReporter) { 167 if (explicitBindingConfictsWithInject(duplicateBindings.keySet())) { 168 compilerOptions 169 .explicitBindingConflictsWithInjectValidationType() 170 .diagnosticKind() 171 .ifPresent( 172 diagnosticKind -> 173 reportExplicitBindingConflictsWithInject( 174 duplicateBindings.values(), 175 diagnosticReporter, 176 diagnosticKind, 177 bindingGraph.rootComponentNode())); 178 return; 179 } 180 181 reportDuplicateBindings(duplicateBindings.values(), bindingGraph, diagnosticReporter); 182 } 183 184 /** 185 * Returns {@code true} if the bindings contain one {@code @Inject} binding and one that isn't. 186 */ explicitBindingConfictsWithInject( ImmutableSet<BindingWithoutComponent> duplicateBindings)187 private static boolean explicitBindingConfictsWithInject( 188 ImmutableSet<BindingWithoutComponent> duplicateBindings) { 189 ImmutableMultiset<BindingKind> bindingKinds = 190 Multimaps.index(duplicateBindings, BindingWithoutComponent::bindingKind).keys(); 191 return bindingKinds.count(INJECTION) == 1 && bindingKinds.size() == 2; 192 } 193 reportExplicitBindingConflictsWithInject( ImmutableCollection<Binding> duplicateBindings, DiagnosticReporter diagnosticReporter, Diagnostic.Kind diagnosticKind, ComponentNode rootComponent)194 private void reportExplicitBindingConflictsWithInject( 195 ImmutableCollection<Binding> duplicateBindings, 196 DiagnosticReporter diagnosticReporter, 197 Diagnostic.Kind diagnosticKind, 198 ComponentNode rootComponent) { 199 Binding injectBinding = rootmostBindingWithKind(k -> k.equals(INJECTION), duplicateBindings); 200 Binding explicitBinding = rootmostBindingWithKind(k -> !k.equals(INJECTION), duplicateBindings); 201 StringBuilder message = 202 new StringBuilder() 203 .append(explicitBinding.key()) 204 .append(" is bound multiple times:") 205 .append(formatWithComponentPath(injectBinding)) 206 .append(formatWithComponentPath(explicitBinding)) 207 .append( 208 "\nThis condition was never validated before, and will soon be an error. " 209 + "See https://dagger.dev/conflicting-inject."); 210 211 if (compilerOptions.experimentalDaggerErrorMessages()) { 212 diagnosticReporter.reportComponent(diagnosticKind, rootComponent, message.toString()); 213 } else { 214 diagnosticReporter.reportBinding(diagnosticKind, explicitBinding, message.toString()); 215 } 216 } 217 formatWithComponentPath(Binding binding)218 private String formatWithComponentPath(Binding binding) { 219 return String.format( 220 "\n%s%s [%s]", 221 Formatter.INDENT, 222 declarationFormatter.format(((BindingNode) binding).delegate()), 223 binding.componentPath()); 224 } 225 reportDuplicateBindings( ImmutableCollection<Binding> duplicateBindings, BindingGraph graph, DiagnosticReporter diagnosticReporter)226 private void reportDuplicateBindings( 227 ImmutableCollection<Binding> duplicateBindings, 228 BindingGraph graph, 229 DiagnosticReporter diagnosticReporter) { 230 StringBuilder message = new StringBuilder(); 231 Binding oneBinding = duplicateBindings.asList().get(0); 232 ImmutableSet<Binding> multibindings = 233 duplicateBindings.stream() 234 .filter(binding -> binding.kind().isMultibinding()) 235 .collect(toImmutableSet()); 236 if (multibindings.isEmpty()) { 237 message.append(oneBinding.key()).append(" is bound multiple times:"); 238 formatDeclarations(message, 2, declarations(graph, duplicateBindings)); 239 } else { 240 Binding oneMultibinding = multibindings.asList().get(0); 241 message.append(oneMultibinding.key()).append(" has incompatible bindings or declarations:\n"); 242 message 243 .append(INDENT) 244 .append(multibindingTypeString(oneMultibinding)) 245 .append(" bindings and declarations:"); 246 formatDeclarations(message, 2, declarations(graph, multibindings)); 247 ImmutableSet<Declaration> uniqueBindingDeclarations = 248 duplicateBindings.stream() 249 .filter(binding -> !binding.kind().isMultibinding()) 250 .flatMap(binding -> declarations(graph, binding).stream()) 251 .filter(declaration -> !(declaration instanceof MultibindingDeclaration)) 252 .collect(toImmutableSet()); 253 if (!uniqueBindingDeclarations.isEmpty()) { 254 message.append('\n').append(INDENT).append("Unique bindings and declarations:"); 255 formatDeclarations(message, 2, uniqueBindingDeclarations); 256 } 257 } 258 259 if (compilerOptions.experimentalDaggerErrorMessages()) { 260 message.append(String.format("\n%sin component: [%s]", INDENT, oneBinding.componentPath())); 261 diagnosticReporter.reportComponent(ERROR, graph.rootComponentNode(), message.toString()); 262 } else { 263 diagnosticReporter.reportBinding(ERROR, oneBinding, message.toString()); 264 } 265 } 266 formatDeclarations( StringBuilder builder, int indentLevel, Iterable<? extends Declaration> bindingDeclarations)267 private void formatDeclarations( 268 StringBuilder builder, 269 int indentLevel, 270 Iterable<? extends Declaration> bindingDeclarations) { 271 declarationFormatter.formatIndentedList( 272 builder, ImmutableList.copyOf(bindingDeclarations), indentLevel); 273 } 274 declarations( BindingGraph graph, ImmutableCollection<Binding> bindings)275 private ImmutableSet<Declaration> declarations( 276 BindingGraph graph, ImmutableCollection<Binding> bindings) { 277 return bindings.stream() 278 .flatMap(binding -> declarations(graph, binding).stream()) 279 .distinct() 280 .sorted(Declaration.COMPARATOR) 281 .collect(toImmutableSet()); 282 } 283 declarations(BindingGraph graph, Binding binding)284 private ImmutableSet<Declaration> declarations(BindingGraph graph, Binding binding) { 285 ImmutableSet.Builder<Declaration> declarations = ImmutableSet.builder(); 286 BindingNode bindingNode = (BindingNode) binding; 287 bindingNode.associatedDeclarations().forEach(declarations::add); 288 if (declarationFormatter.canFormat(bindingNode.delegate())) { 289 declarations.add(bindingNode.delegate()); 290 } else { 291 graph.requestedBindings(binding).stream() 292 .flatMap(requestedBinding -> declarations(graph, requestedBinding).stream()) 293 .forEach(declarations::add); 294 } 295 return declarations.build(); 296 } 297 multibindingTypeString(Binding multibinding)298 private String multibindingTypeString(Binding multibinding) { 299 switch (multibinding.kind()) { 300 case MULTIBOUND_MAP: 301 return "Map"; 302 case MULTIBOUND_SET: 303 return "Set"; 304 default: 305 throw new AssertionError(multibinding); 306 } 307 } 308 valueSetsForEachKey(Multimap<?, E> multimap)309 private static <E> ImmutableSet<ImmutableSet<E>> valueSetsForEachKey(Multimap<?, E> multimap) { 310 return multimap.asMap().values().stream().map(ImmutableSet::copyOf).collect(toImmutableSet()); 311 } 312 313 /** Returns the binding of the given kind that is closest to the root component. */ rootmostBindingWithKind( Predicate<BindingKind> bindingKindPredicate, ImmutableCollection<Binding> bindings)314 private static Binding rootmostBindingWithKind( 315 Predicate<BindingKind> bindingKindPredicate, ImmutableCollection<Binding> bindings) { 316 return bindings.stream() 317 .filter(b -> bindingKindPredicate.test(b.kind())) 318 .min(BY_LENGTH_OF_COMPONENT_PATH) 319 .get(); 320 } 321 322 /** The identifying information about a binding, excluding its {@link Binding#componentPath()}. */ 323 @AutoValue 324 abstract static class BindingWithoutComponent { 325 bindingKind()326 abstract BindingKind bindingKind(); 327 bindingKey()328 abstract Key bindingKey(); 329 bindingElement()330 abstract Optional<XElement> bindingElement(); 331 contributingModule()332 abstract Optional<XTypeElement> contributingModule(); 333 index(Set<Binding> bindings)334 static ImmutableSetMultimap<BindingWithoutComponent, Binding> index(Set<Binding> bindings) { 335 return bindings.stream() 336 .collect(toImmutableSetMultimap(BindingWithoutComponent::forBinding, b -> b)); 337 } 338 forBinding(Binding binding)339 private static BindingWithoutComponent forBinding(Binding binding) { 340 return new AutoValue_DuplicateBindingsValidator_BindingWithoutComponent( 341 binding.kind(), 342 binding.key(), 343 binding.bindingElement().map(DaggerElement::xprocessing), 344 binding.contributingModule().map(DaggerTypeElement::xprocessing)); 345 } 346 } 347 348 349 /** The identifying information about a key with the given type equivalence. */ 350 @AutoValue 351 abstract static class KeyWithTypeEquivalence { qualifier()352 abstract Optional<DaggerAnnotation> qualifier(); 353 wrappedType()354 abstract Equivalence.Wrapper<XType> wrappedType(); 355 multibindingContributionIdentifier()356 abstract Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier(); 357 forKey(Key key, Equivalence<XType> typeEquivalence)358 private static KeyWithTypeEquivalence forKey(Key key, Equivalence<XType> typeEquivalence) { 359 return new AutoValue_DuplicateBindingsValidator_KeyWithTypeEquivalence( 360 key.qualifier(), 361 typeEquivalence.wrap(key.type().xprocessing()), 362 key.multibindingContributionIdentifier()); 363 } 364 } 365 } 366