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