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.validation; 18 19 import static com.google.auto.common.MoreTypes.asDeclared; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.base.Predicates.in; 22 import static com.google.common.collect.Collections2.transform; 23 import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnnotation; 24 import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes; 25 import static dagger.internal.codegen.base.Formatter.INDENT; 26 import static dagger.internal.codegen.base.Scopes.getReadableSource; 27 import static dagger.internal.codegen.base.Scopes.scopesOf; 28 import static dagger.internal.codegen.base.Scopes.singletonScope; 29 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; 30 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 31 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; 32 import static java.util.stream.Collectors.joining; 33 import static java.util.stream.Collectors.toList; 34 import static javax.tools.Diagnostic.Kind.ERROR; 35 36 import com.google.auto.common.MoreElements; 37 import com.google.auto.common.MoreTypes; 38 import com.google.common.base.Equivalence.Wrapper; 39 import com.google.common.collect.ImmutableSet; 40 import com.google.common.collect.ImmutableSetMultimap; 41 import com.google.common.collect.Multimaps; 42 import com.google.common.collect.Sets; 43 import dagger.internal.codegen.binding.ComponentCreatorDescriptor; 44 import dagger.internal.codegen.binding.ComponentDescriptor; 45 import dagger.internal.codegen.binding.ComponentRequirement; 46 import dagger.internal.codegen.binding.ComponentRequirement.NullPolicy; 47 import dagger.internal.codegen.binding.ContributionBinding; 48 import dagger.internal.codegen.binding.ErrorMessages; 49 import dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages; 50 import dagger.internal.codegen.binding.MethodSignatureFormatter; 51 import dagger.internal.codegen.binding.ModuleDescriptor; 52 import dagger.internal.codegen.compileroption.CompilerOptions; 53 import dagger.internal.codegen.compileroption.ValidationType; 54 import dagger.internal.codegen.kotlin.KotlinMetadataUtil; 55 import dagger.internal.codegen.langmodel.DaggerElements; 56 import dagger.internal.codegen.langmodel.DaggerTypes; 57 import dagger.model.Scope; 58 import java.util.ArrayDeque; 59 import java.util.Collection; 60 import java.util.Deque; 61 import java.util.LinkedHashMap; 62 import java.util.Map; 63 import java.util.Map.Entry; 64 import java.util.Optional; 65 import java.util.Set; 66 import java.util.StringJoiner; 67 import javax.inject.Inject; 68 import javax.lang.model.element.Element; 69 import javax.lang.model.element.ExecutableElement; 70 import javax.lang.model.element.Modifier; 71 import javax.lang.model.element.TypeElement; 72 import javax.lang.model.element.VariableElement; 73 import javax.lang.model.type.DeclaredType; 74 import javax.lang.model.type.ExecutableType; 75 import javax.lang.model.type.TypeMirror; 76 import javax.tools.Diagnostic; 77 78 /** 79 * Reports errors in the component hierarchy. 80 * 81 * <ul> 82 * <li>Validates scope hierarchy of component dependencies and subcomponents. 83 * <li>Reports errors if there are component dependency cycles. 84 * <li>Reports errors if any abstract modules have non-abstract instance binding methods. 85 * <li>Validates component creator types. 86 * </ul> 87 */ 88 // TODO(dpb): Combine with ComponentHierarchyValidator. 89 public final class ComponentDescriptorValidator { 90 91 private final DaggerElements elements; 92 private final DaggerTypes types; 93 private final CompilerOptions compilerOptions; 94 private final MethodSignatureFormatter methodSignatureFormatter; 95 private final ComponentHierarchyValidator componentHierarchyValidator; 96 private final KotlinMetadataUtil metadataUtil; 97 98 @Inject ComponentDescriptorValidator( DaggerElements elements, DaggerTypes types, CompilerOptions compilerOptions, MethodSignatureFormatter methodSignatureFormatter, ComponentHierarchyValidator componentHierarchyValidator, KotlinMetadataUtil metadataUtil)99 ComponentDescriptorValidator( 100 DaggerElements elements, 101 DaggerTypes types, 102 CompilerOptions compilerOptions, 103 MethodSignatureFormatter methodSignatureFormatter, 104 ComponentHierarchyValidator componentHierarchyValidator, 105 KotlinMetadataUtil metadataUtil) { 106 this.elements = elements; 107 this.types = types; 108 this.compilerOptions = compilerOptions; 109 this.methodSignatureFormatter = methodSignatureFormatter; 110 this.componentHierarchyValidator = componentHierarchyValidator; 111 this.metadataUtil = metadataUtil; 112 } 113 validate(ComponentDescriptor component)114 public ValidationReport<TypeElement> validate(ComponentDescriptor component) { 115 ComponentValidation validation = new ComponentValidation(component); 116 validation.visitComponent(component); 117 validation.report(component).addSubreport(componentHierarchyValidator.validate(component)); 118 return validation.buildReport(); 119 } 120 121 private final class ComponentValidation { 122 final ComponentDescriptor rootComponent; 123 final Map<ComponentDescriptor, ValidationReport.Builder<TypeElement>> reports = 124 new LinkedHashMap<>(); 125 ComponentValidation(ComponentDescriptor rootComponent)126 ComponentValidation(ComponentDescriptor rootComponent) { 127 this.rootComponent = checkNotNull(rootComponent); 128 } 129 130 /** Returns a report that contains all validation messages found during traversal. */ buildReport()131 ValidationReport<TypeElement> buildReport() { 132 ValidationReport.Builder<TypeElement> report = 133 ValidationReport.about(rootComponent.typeElement()); 134 reports.values().forEach(subreport -> report.addSubreport(subreport.build())); 135 return report.build(); 136 } 137 138 /** Returns the report builder for a (sub)component. */ report(ComponentDescriptor component)139 private ValidationReport.Builder<TypeElement> report(ComponentDescriptor component) { 140 return reentrantComputeIfAbsent( 141 reports, component, descriptor -> ValidationReport.about(descriptor.typeElement())); 142 } 143 reportComponentItem( Diagnostic.Kind kind, ComponentDescriptor component, String message)144 private void reportComponentItem( 145 Diagnostic.Kind kind, ComponentDescriptor component, String message) { 146 report(component) 147 .addItem(message, kind, component.typeElement(), component.annotation().annotation()); 148 } 149 reportComponentError(ComponentDescriptor component, String error)150 private void reportComponentError(ComponentDescriptor component, String error) { 151 reportComponentItem(ERROR, component, error); 152 } 153 visitComponent(ComponentDescriptor component)154 void visitComponent(ComponentDescriptor component) { 155 validateDependencyScopes(component); 156 validateComponentDependencyHierarchy(component); 157 validateModules(component); 158 validateCreators(component); 159 component.childComponents().forEach(this::visitComponent); 160 } 161 162 /** Validates that component dependencies do not form a cycle. */ validateComponentDependencyHierarchy(ComponentDescriptor component)163 private void validateComponentDependencyHierarchy(ComponentDescriptor component) { 164 validateComponentDependencyHierarchy(component, component.typeElement(), new ArrayDeque<>()); 165 } 166 167 /** Recursive method to validate that component dependencies do not form a cycle. */ validateComponentDependencyHierarchy( ComponentDescriptor component, TypeElement dependency, Deque<TypeElement> dependencyStack)168 private void validateComponentDependencyHierarchy( 169 ComponentDescriptor component, TypeElement dependency, Deque<TypeElement> dependencyStack) { 170 if (dependencyStack.contains(dependency)) { 171 // Current component has already appeared in the component chain. 172 StringBuilder message = new StringBuilder(); 173 message.append(component.typeElement().getQualifiedName()); 174 message.append(" contains a cycle in its component dependencies:\n"); 175 dependencyStack.push(dependency); 176 appendIndentedComponentsList(message, dependencyStack); 177 dependencyStack.pop(); 178 reportComponentItem( 179 compilerOptions.scopeCycleValidationType().diagnosticKind().get(), 180 component, 181 message.toString()); 182 } else if (compilerOptions.validateTransitiveComponentDependencies() 183 // Always validate direct component dependencies referenced by this component regardless 184 // of the flag value 185 || dependencyStack.isEmpty()) { 186 rootComponentAnnotation(dependency) 187 .ifPresent( 188 componentAnnotation -> { 189 dependencyStack.push(dependency); 190 191 for (TypeElement nextDependency : componentAnnotation.dependencies()) { 192 validateComponentDependencyHierarchy( 193 component, nextDependency, dependencyStack); 194 } 195 196 dependencyStack.pop(); 197 }); 198 } 199 } 200 201 /** 202 * Validates that among the dependencies there are no cycles within the scoping chain, and that 203 * singleton components have no scoped dependencies. 204 */ validateDependencyScopes(ComponentDescriptor component)205 private void validateDependencyScopes(ComponentDescriptor component) { 206 ImmutableSet<Scope> scopes = component.scopes(); 207 ImmutableSet<TypeElement> scopedDependencies = 208 scopedTypesIn( 209 component 210 .dependencies() 211 .stream() 212 .map(ComponentRequirement::typeElement) 213 .collect(toImmutableSet())); 214 if (!scopes.isEmpty()) { 215 Scope singletonScope = singletonScope(elements); 216 // Dagger 1.x scope compatibility requires this be suppress-able. 217 if (compilerOptions.scopeCycleValidationType().diagnosticKind().isPresent() 218 && scopes.contains(singletonScope)) { 219 // Singleton is a special-case representing the longest lifetime, and therefore 220 // @Singleton components may not depend on scoped components 221 if (!scopedDependencies.isEmpty()) { 222 StringBuilder message = 223 new StringBuilder( 224 "This @Singleton component cannot depend on scoped components:\n"); 225 appendIndentedComponentsList(message, scopedDependencies); 226 reportComponentItem( 227 compilerOptions.scopeCycleValidationType().diagnosticKind().get(), 228 component, 229 message.toString()); 230 } 231 } else { 232 // Dagger 1.x scope compatibility requires this be suppress-able. 233 if (!compilerOptions.scopeCycleValidationType().equals(ValidationType.NONE)) { 234 validateDependencyScopeHierarchy( 235 component, component.typeElement(), new ArrayDeque<>(), new ArrayDeque<>()); 236 } 237 } 238 } else { 239 // Scopeless components may not depend on scoped components. 240 if (!scopedDependencies.isEmpty()) { 241 StringBuilder message = 242 new StringBuilder(component.typeElement().getQualifiedName()) 243 .append(" (unscoped) cannot depend on scoped components:\n"); 244 appendIndentedComponentsList(message, scopedDependencies); 245 reportComponentError(component, message.toString()); 246 } 247 } 248 } 249 validateModules(ComponentDescriptor component)250 private void validateModules(ComponentDescriptor component) { 251 for (ModuleDescriptor module : component.modules()) { 252 if (module.moduleElement().getModifiers().contains(Modifier.ABSTRACT)) { 253 for (ContributionBinding binding : module.bindings()) { 254 if (binding.requiresModuleInstance()) { 255 report(component).addError(abstractModuleHasInstanceBindingMethodsError(module)); 256 break; 257 } 258 } 259 } 260 } 261 } 262 abstractModuleHasInstanceBindingMethodsError(ModuleDescriptor module)263 private String abstractModuleHasInstanceBindingMethodsError(ModuleDescriptor module) { 264 String methodAnnotations; 265 switch (module.kind()) { 266 case MODULE: 267 methodAnnotations = "@Provides"; 268 break; 269 case PRODUCER_MODULE: 270 methodAnnotations = "@Provides or @Produces"; 271 break; 272 default: 273 throw new AssertionError(module.kind()); 274 } 275 return String.format( 276 "%s is abstract and has instance %s methods. Consider making the methods static or " 277 + "including a non-abstract subclass of the module instead.", 278 module.moduleElement(), methodAnnotations); 279 } 280 validateCreators(ComponentDescriptor component)281 private void validateCreators(ComponentDescriptor component) { 282 if (!component.creatorDescriptor().isPresent()) { 283 // If no builder, nothing to validate. 284 return; 285 } 286 287 ComponentCreatorDescriptor creator = component.creatorDescriptor().get(); 288 ComponentCreatorMessages messages = ErrorMessages.creatorMessagesFor(creator.annotation()); 289 290 // Requirements for modules and dependencies that the creator can set 291 Set<ComponentRequirement> creatorModuleAndDependencyRequirements = 292 creator.moduleAndDependencyRequirements(); 293 // Modules and dependencies the component requires 294 Set<ComponentRequirement> componentModuleAndDependencyRequirements = 295 component.dependenciesAndConcreteModules(); 296 297 // Requirements that the creator can set that don't match any requirements that the component 298 // actually has. 299 Set<ComponentRequirement> inapplicableRequirementsOnCreator = 300 Sets.difference( 301 creatorModuleAndDependencyRequirements, componentModuleAndDependencyRequirements); 302 303 DeclaredType container = asDeclared(creator.typeElement().asType()); 304 if (!inapplicableRequirementsOnCreator.isEmpty()) { 305 Collection<Element> excessElements = 306 Multimaps.filterKeys( 307 creator.unvalidatedRequirementElements(), in(inapplicableRequirementsOnCreator)) 308 .values(); 309 String formatted = 310 excessElements.stream() 311 .map(element -> formatElement(element, container)) 312 .collect(joining(", ", "[", "]")); 313 report(component) 314 .addError(String.format(messages.extraSetters(), formatted), creator.typeElement()); 315 } 316 317 // Component requirements that the creator must be able to set 318 Set<ComponentRequirement> mustBePassed = 319 Sets.filter( 320 componentModuleAndDependencyRequirements, 321 input -> input.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.THROW)); 322 // Component requirements that the creator must be able to set, but can't 323 Set<ComponentRequirement> missingRequirements = 324 Sets.difference(mustBePassed, creatorModuleAndDependencyRequirements); 325 326 if (!missingRequirements.isEmpty()) { 327 report(component) 328 .addError( 329 String.format( 330 messages.missingSetters(), 331 missingRequirements.stream().map(ComponentRequirement::type).collect(toList())), 332 creator.typeElement()); 333 } 334 335 // Validate that declared creator requirements (modules, dependencies) have unique types. 336 ImmutableSetMultimap<Wrapper<TypeMirror>, Element> declaredRequirementsByType = 337 Multimaps.filterKeys( 338 creator.unvalidatedRequirementElements(), 339 creatorModuleAndDependencyRequirements::contains) 340 .entries().stream() 341 .collect( 342 toImmutableSetMultimap(entry -> entry.getKey().wrappedType(), Entry::getValue)); 343 declaredRequirementsByType 344 .asMap() 345 .forEach( 346 (typeWrapper, elementsForType) -> { 347 if (elementsForType.size() > 1) { 348 TypeMirror type = typeWrapper.get(); 349 // TODO(cgdecker): Attach this error message to the factory method rather than 350 // the component type if the elements are factory method parameters AND the 351 // factory method is defined by the factory type itself and not by a supertype. 352 report(component) 353 .addError( 354 String.format( 355 messages.multipleSettersForModuleOrDependencyType(), 356 type, 357 transform( 358 elementsForType, element -> formatElement(element, container))), 359 creator.typeElement()); 360 } 361 }); 362 363 // TODO(cgdecker): Duplicate binding validation should handle the case of multiple elements 364 // that set the same bound-instance Key, but validating that here would make it fail faster 365 // for subcomponents. 366 } 367 formatElement(Element element, DeclaredType container)368 private String formatElement(Element element, DeclaredType container) { 369 // TODO(cgdecker): Extract some or all of this to another class? 370 // But note that it does different formatting for parameters than 371 // DaggerElements.elementToString(Element). 372 switch (element.getKind()) { 373 case METHOD: 374 return methodSignatureFormatter.format( 375 MoreElements.asExecutable(element), Optional.of(container)); 376 case PARAMETER: 377 return formatParameter(MoreElements.asVariable(element), container); 378 default: 379 // This method shouldn't be called with any other type of element. 380 throw new AssertionError(); 381 } 382 } 383 formatParameter(VariableElement parameter, DeclaredType container)384 private String formatParameter(VariableElement parameter, DeclaredType container) { 385 // TODO(cgdecker): Possibly leave the type (and annotations?) off of the parameters here and 386 // just use their names, since the type will be redundant in the context of the error message. 387 StringJoiner joiner = new StringJoiner(" "); 388 parameter.getAnnotationMirrors().stream().map(Object::toString).forEach(joiner::add); 389 TypeMirror parameterType = resolveParameterType(parameter, container); 390 return joiner 391 .add(stripCommonTypePrefixes(parameterType.toString())) 392 .add(parameter.getSimpleName()) 393 .toString(); 394 } 395 resolveParameterType(VariableElement parameter, DeclaredType container)396 private TypeMirror resolveParameterType(VariableElement parameter, DeclaredType container) { 397 ExecutableElement method = 398 MoreElements.asExecutable(parameter.getEnclosingElement()); 399 int parameterIndex = method.getParameters().indexOf(parameter); 400 401 ExecutableType methodType = MoreTypes.asExecutable(types.asMemberOf(container, method)); 402 return methodType.getParameterTypes().get(parameterIndex); 403 } 404 405 /** 406 * Validates that scopes do not participate in a scoping cycle - that is to say, scoped 407 * components are in a hierarchical relationship terminating with Singleton. 408 * 409 * <p>As a side-effect, this means scoped components cannot have a dependency cycle between 410 * themselves, since a component's presence within its own dependency path implies a cyclical 411 * relationship between scopes. However, cycles in component dependencies are explicitly checked 412 * in {@link #validateComponentDependencyHierarchy(ComponentDescriptor)}. 413 */ validateDependencyScopeHierarchy( ComponentDescriptor component, TypeElement dependency, Deque<ImmutableSet<Scope>> scopeStack, Deque<TypeElement> scopedDependencyStack)414 private void validateDependencyScopeHierarchy( 415 ComponentDescriptor component, 416 TypeElement dependency, 417 Deque<ImmutableSet<Scope>> scopeStack, 418 Deque<TypeElement> scopedDependencyStack) { 419 ImmutableSet<Scope> scopes = scopesOf(dependency); 420 if (stackOverlaps(scopeStack, scopes)) { 421 scopedDependencyStack.push(dependency); 422 // Current scope has already appeared in the component chain. 423 StringBuilder message = new StringBuilder(); 424 message.append(component.typeElement().getQualifiedName()); 425 message.append(" depends on scoped components in a non-hierarchical scope ordering:\n"); 426 appendIndentedComponentsList(message, scopedDependencyStack); 427 if (compilerOptions.scopeCycleValidationType().diagnosticKind().isPresent()) { 428 reportComponentItem( 429 compilerOptions.scopeCycleValidationType().diagnosticKind().get(), 430 component, 431 message.toString()); 432 } 433 scopedDependencyStack.pop(); 434 } else if (compilerOptions.validateTransitiveComponentDependencies() 435 // Always validate direct component dependencies referenced by this component regardless 436 // of the flag value 437 || scopedDependencyStack.isEmpty()) { 438 // TODO(beder): transitively check scopes of production components too. 439 rootComponentAnnotation(dependency) 440 .filter(componentAnnotation -> !componentAnnotation.isProduction()) 441 .ifPresent( 442 componentAnnotation -> { 443 ImmutableSet<TypeElement> scopedDependencies = 444 scopedTypesIn(componentAnnotation.dependencies()); 445 if (!scopedDependencies.isEmpty()) { 446 // empty can be ignored (base-case) 447 scopeStack.push(scopes); 448 scopedDependencyStack.push(dependency); 449 for (TypeElement scopedDependency : scopedDependencies) { 450 validateDependencyScopeHierarchy( 451 component, 452 scopedDependency, 453 scopeStack, 454 scopedDependencyStack); 455 } 456 scopedDependencyStack.pop(); 457 scopeStack.pop(); 458 } 459 }); // else: we skip component dependencies which are not components 460 } 461 } 462 stackOverlaps(Deque<ImmutableSet<T>> stack, ImmutableSet<T> set)463 private <T> boolean stackOverlaps(Deque<ImmutableSet<T>> stack, ImmutableSet<T> set) { 464 for (ImmutableSet<T> entry : stack) { 465 if (!Sets.intersection(entry, set).isEmpty()) { 466 return true; 467 } 468 } 469 return false; 470 } 471 472 /** Appends and formats a list of indented component types (with their scope annotations). */ appendIndentedComponentsList(StringBuilder message, Iterable<TypeElement> types)473 private void appendIndentedComponentsList(StringBuilder message, Iterable<TypeElement> types) { 474 for (TypeElement scopedComponent : types) { 475 message.append(INDENT); 476 for (Scope scope : scopesOf(scopedComponent)) { 477 message.append(getReadableSource(scope)).append(' '); 478 } 479 message 480 .append(stripCommonTypePrefixes(scopedComponent.getQualifiedName().toString())) 481 .append('\n'); 482 } 483 } 484 485 /** 486 * Returns a set of type elements containing only those found in the input set that have a 487 * scoping annotation. 488 */ scopedTypesIn(Collection<TypeElement> types)489 private ImmutableSet<TypeElement> scopedTypesIn(Collection<TypeElement> types) { 490 return types.stream().filter(type -> !scopesOf(type).isEmpty()).collect(toImmutableSet()); 491 } 492 } 493 } 494