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 androidx.room.compiler.processing.XElementKt.isMethod; 20 import static androidx.room.compiler.processing.XElementKt.isMethodParameter; 21 import static com.google.common.base.Preconditions.checkArgument; 22 import static com.google.common.base.Preconditions.checkNotNull; 23 import static com.google.common.base.Predicates.in; 24 import static com.google.common.collect.Collections2.transform; 25 import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnnotation; 26 import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes; 27 import static dagger.internal.codegen.base.Formatter.INDENT; 28 import static dagger.internal.codegen.base.Scopes.getReadableSource; 29 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; 30 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 31 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 32 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; 33 import static dagger.internal.codegen.xprocessing.XElements.asMethod; 34 import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter; 35 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 36 import static java.util.stream.Collectors.joining; 37 import static javax.tools.Diagnostic.Kind.ERROR; 38 39 import androidx.room.compiler.processing.XAnnotation; 40 import androidx.room.compiler.processing.XElement; 41 import androidx.room.compiler.processing.XExecutableParameterElement; 42 import androidx.room.compiler.processing.XMethodElement; 43 import androidx.room.compiler.processing.XMethodType; 44 import androidx.room.compiler.processing.XType; 45 import androidx.room.compiler.processing.XTypeElement; 46 import com.google.common.collect.ImmutableSet; 47 import com.google.common.collect.ImmutableSetMultimap; 48 import com.google.common.collect.Multimaps; 49 import com.google.common.collect.Sets; 50 import com.squareup.javapoet.ClassName; 51 import com.squareup.javapoet.TypeName; 52 import dagger.internal.codegen.base.DaggerSuperficialValidation; 53 import dagger.internal.codegen.binding.ComponentCreatorDescriptor; 54 import dagger.internal.codegen.binding.ComponentDescriptor; 55 import dagger.internal.codegen.binding.ComponentRequirement; 56 import dagger.internal.codegen.binding.ComponentRequirement.NullPolicy; 57 import dagger.internal.codegen.binding.ContributionBinding; 58 import dagger.internal.codegen.binding.ErrorMessages; 59 import dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages; 60 import dagger.internal.codegen.binding.InjectionAnnotations; 61 import dagger.internal.codegen.binding.MethodSignatureFormatter; 62 import dagger.internal.codegen.binding.ModuleDescriptor; 63 import dagger.internal.codegen.compileroption.CompilerOptions; 64 import dagger.internal.codegen.compileroption.ValidationType; 65 import dagger.internal.codegen.javapoet.TypeNames; 66 import dagger.internal.codegen.model.Scope; 67 import dagger.internal.codegen.xprocessing.XTypes; 68 import java.util.ArrayDeque; 69 import java.util.Collection; 70 import java.util.Deque; 71 import java.util.LinkedHashMap; 72 import java.util.Map; 73 import java.util.Map.Entry; 74 import java.util.Optional; 75 import java.util.Set; 76 import java.util.StringJoiner; 77 import javax.inject.Inject; 78 import javax.tools.Diagnostic; 79 80 /** 81 * Reports errors in the component hierarchy. 82 * 83 * <ul> 84 * <li>Validates scope hierarchy of component dependencies and subcomponents. 85 * <li>Reports errors if there are component dependency cycles. 86 * <li>Reports errors if any abstract modules have non-abstract instance binding methods. 87 * <li>Validates component creator types. 88 * </ul> 89 */ 90 // TODO(dpb): Combine with ComponentHierarchyValidator. 91 public final class ComponentDescriptorValidator { 92 93 private final CompilerOptions compilerOptions; 94 private final MethodSignatureFormatter methodSignatureFormatter; 95 private final ComponentHierarchyValidator componentHierarchyValidator; 96 private final InjectionAnnotations injectionAnnotations; 97 private final DaggerSuperficialValidation superficialValidation; 98 99 @Inject ComponentDescriptorValidator( CompilerOptions compilerOptions, MethodSignatureFormatter methodSignatureFormatter, ComponentHierarchyValidator componentHierarchyValidator, InjectionAnnotations injectionAnnotations, DaggerSuperficialValidation superficialValidation)100 ComponentDescriptorValidator( 101 CompilerOptions compilerOptions, 102 MethodSignatureFormatter methodSignatureFormatter, 103 ComponentHierarchyValidator componentHierarchyValidator, 104 InjectionAnnotations injectionAnnotations, 105 DaggerSuperficialValidation superficialValidation) { 106 this.compilerOptions = compilerOptions; 107 this.methodSignatureFormatter = methodSignatureFormatter; 108 this.componentHierarchyValidator = componentHierarchyValidator; 109 this.injectionAnnotations = injectionAnnotations; 110 this.superficialValidation = superficialValidation; 111 } 112 validate(ComponentDescriptor component)113 public ValidationReport validate(ComponentDescriptor component) { 114 ComponentValidation validation = new ComponentValidation(component); 115 validation.visitComponent(component); 116 validation.report(component).addSubreport(componentHierarchyValidator.validate(component)); 117 return validation.buildReport(); 118 } 119 120 private final class ComponentValidation { 121 final ComponentDescriptor rootComponent; 122 final Map<ComponentDescriptor, ValidationReport.Builder> reports = new LinkedHashMap<>(); 123 ComponentValidation(ComponentDescriptor rootComponent)124 ComponentValidation(ComponentDescriptor rootComponent) { 125 this.rootComponent = checkNotNull(rootComponent); 126 } 127 128 /** Returns a report that contains all validation messages found during traversal. */ buildReport()129 ValidationReport buildReport() { 130 ValidationReport.Builder report = ValidationReport.about(rootComponent.typeElement()); 131 reports.values().forEach(subreport -> report.addSubreport(subreport.build())); 132 return report.build(); 133 } 134 135 /** Returns the report builder for a (sub)component. */ report(ComponentDescriptor component)136 private ValidationReport.Builder report(ComponentDescriptor component) { 137 return reentrantComputeIfAbsent( 138 reports, component, descriptor -> ValidationReport.about(descriptor.typeElement())); 139 } 140 reportComponentItem( Diagnostic.Kind kind, ComponentDescriptor component, String message)141 private void reportComponentItem( 142 Diagnostic.Kind kind, ComponentDescriptor component, String message) { 143 report(component) 144 .addItem(message, kind, component.typeElement(), component.annotation().annotation()); 145 } 146 reportComponentError(ComponentDescriptor component, String error)147 private void reportComponentError(ComponentDescriptor component, String error) { 148 reportComponentItem(ERROR, component, error); 149 } 150 visitComponent(ComponentDescriptor component)151 void visitComponent(ComponentDescriptor component) { 152 validateDependencyScopes(component); 153 validateComponentDependencyHierarchy(component); 154 validateModules(component); 155 validateCreators(component); 156 component.childComponents().forEach(this::visitComponent); 157 } 158 159 /** Validates that component dependencies do not form a cycle. */ validateComponentDependencyHierarchy(ComponentDescriptor component)160 private void validateComponentDependencyHierarchy(ComponentDescriptor component) { 161 validateComponentDependencyHierarchy(component, component.typeElement(), new ArrayDeque<>()); 162 } 163 164 /** Recursive method to validate that component dependencies do not form a cycle. */ validateComponentDependencyHierarchy( ComponentDescriptor component, XTypeElement dependency, Deque<XTypeElement> dependencyStack)165 private void validateComponentDependencyHierarchy( 166 ComponentDescriptor component, 167 XTypeElement dependency, 168 Deque<XTypeElement> dependencyStack) { 169 if (dependencyStack.contains(dependency)) { 170 // Current component has already appeared in the component chain. 171 StringBuilder message = new StringBuilder(); 172 message.append(component.typeElement().getQualifiedName()); 173 message.append(" contains a cycle in its component dependencies:\n"); 174 dependencyStack.push(dependency); 175 appendIndentedComponentsList(message, dependencyStack); 176 dependencyStack.pop(); 177 reportComponentItem( 178 compilerOptions.scopeCycleValidationType().diagnosticKind().get(), 179 component, 180 message.toString()); 181 } else if (compilerOptions.validateTransitiveComponentDependencies() 182 // Always validate direct component dependencies referenced by this component regardless 183 // of the flag value 184 || dependencyStack.isEmpty()) { 185 rootComponentAnnotation(dependency, superficialValidation) 186 .ifPresent( 187 componentAnnotation -> { 188 dependencyStack.push(dependency); 189 for (XTypeElement nextDependency : componentAnnotation.dependencies()) { 190 validateComponentDependencyHierarchy( 191 component, nextDependency, dependencyStack); 192 } 193 dependencyStack.pop(); 194 }); 195 } 196 } 197 198 /** 199 * Validates that among the dependencies there are no cycles within the scoping chain, and that 200 * singleton components have no scoped dependencies. 201 */ validateDependencyScopes(ComponentDescriptor component)202 private void validateDependencyScopes(ComponentDescriptor component) { 203 ImmutableSet<ClassName> scopes = 204 component.scopes().stream().map(Scope::className).collect(toImmutableSet()); 205 ImmutableSet<XTypeElement> scopedDependencies = 206 scopedTypesIn( 207 component.dependencies().stream() 208 .map(ComponentRequirement::typeElement) 209 .collect(toImmutableSet())); 210 if (!scopes.isEmpty()) { 211 // Dagger 1.x scope compatibility requires this be suppress-able. 212 if (compilerOptions.scopeCycleValidationType().diagnosticKind().isPresent() 213 && (scopes.contains(TypeNames.SINGLETON) 214 || scopes.contains(TypeNames.SINGLETON_JAVAX))) { 215 // Singleton is a special-case representing the longest lifetime, and therefore 216 // @Singleton components may not depend on scoped components 217 if (!scopedDependencies.isEmpty()) { 218 StringBuilder message = 219 new StringBuilder( 220 "This @Singleton component cannot depend on scoped components:\n"); 221 appendIndentedComponentsList(message, scopedDependencies); 222 reportComponentItem( 223 compilerOptions.scopeCycleValidationType().diagnosticKind().get(), 224 component, 225 message.toString()); 226 } 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().isAbstract()) { 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 XType container = creator.typeElement().getType(); 300 if (!inapplicableRequirementsOnCreator.isEmpty()) { 301 Collection<XElement> 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().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() 328 .map(ComponentRequirement::type) 329 .map(XTypes::toStableString) 330 .collect(toImmutableList())), 331 creator.typeElement()); 332 } 333 334 // Validate that declared creator requirements (modules, dependencies) have unique types. 335 ImmutableSetMultimap<TypeName, XElement> declaredRequirementsByType = 336 Multimaps.filterKeys( 337 creator.unvalidatedRequirementElements(), 338 creatorModuleAndDependencyRequirements::contains) 339 .entries() 340 .stream() 341 .collect( 342 toImmutableSetMultimap( 343 entry -> entry.getKey().type().getTypeName(), Entry::getValue)); 344 declaredRequirementsByType 345 .asMap() 346 .forEach( 347 (type, elementsForType) -> { 348 if (elementsForType.size() > 1) { 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(XElement element, XType container)368 private String formatElement(XElement element, XType 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 if (isMethod(element)) { 373 return methodSignatureFormatter.format(asMethod(element), Optional.of(container)); 374 } else if (isMethodParameter(element)) { 375 return formatParameter(asMethodParameter(element), container); 376 } 377 // This method shouldn't be called with any other type of element. 378 throw new AssertionError(); 379 } 380 formatParameter(XExecutableParameterElement parameter, XType container)381 private String formatParameter(XExecutableParameterElement parameter, XType container) { 382 // TODO(cgdecker): Possibly leave the type (and annotations?) off of the parameters here and 383 // just use their names, since the type will be redundant in the context of the error message. 384 StringJoiner joiner = new StringJoiner(" "); 385 parameter.getAllAnnotations().stream() 386 .map(XAnnotation::getQualifiedName) 387 .forEach(joiner::add); 388 XType parameterType = resolveParameterType(parameter, container); 389 return joiner 390 .add(stripCommonTypePrefixes(parameterType.getTypeName().toString())) 391 .add(getSimpleName(parameter)) 392 .toString(); 393 } 394 resolveParameterType(XExecutableParameterElement parameter, XType container)395 private XType resolveParameterType(XExecutableParameterElement parameter, XType container) { 396 checkArgument(isMethod(parameter.getEnclosingElement())); 397 XMethodElement method = asMethod(parameter.getEnclosingElement()); 398 int parameterIndex = method.getParameters().indexOf(parameter); 399 400 XMethodType methodType = method.asMemberOf(container); 401 return methodType.getParameterTypes().get(parameterIndex); 402 } 403 404 /** 405 * Validates that scopes do not participate in a scoping cycle - that is to say, scoped 406 * components are in a hierarchical relationship terminating with Singleton. 407 * 408 * <p>As a side-effect, this means scoped components cannot have a dependency cycle between 409 * themselves, since a component's presence within its own dependency path implies a cyclical 410 * relationship between scopes. However, cycles in component dependencies are explicitly checked 411 * in {@link #validateComponentDependencyHierarchy(ComponentDescriptor)}. 412 */ validateDependencyScopeHierarchy( ComponentDescriptor component, XTypeElement dependency, Deque<ImmutableSet<Scope>> scopeStack, Deque<XTypeElement> scopedDependencyStack)413 private void validateDependencyScopeHierarchy( 414 ComponentDescriptor component, 415 XTypeElement dependency, 416 Deque<ImmutableSet<Scope>> scopeStack, 417 Deque<XTypeElement> scopedDependencyStack) { 418 ImmutableSet<Scope> scopes = injectionAnnotations.getScopes(dependency); 419 if (stackOverlaps(scopeStack, scopes)) { 420 scopedDependencyStack.push(dependency); 421 // Current scope has already appeared in the component chain. 422 StringBuilder message = new StringBuilder(); 423 message.append(component.typeElement().getQualifiedName()); 424 message.append(" depends on scoped components in a non-hierarchical scope ordering:\n"); 425 appendIndentedComponentsList(message, scopedDependencyStack); 426 if (compilerOptions.scopeCycleValidationType().diagnosticKind().isPresent()) { 427 reportComponentItem( 428 compilerOptions.scopeCycleValidationType().diagnosticKind().get(), 429 component, 430 message.toString()); 431 } 432 scopedDependencyStack.pop(); 433 } else if (compilerOptions.validateTransitiveComponentDependencies() 434 // Always validate direct component dependencies referenced by this component regardless 435 // of the flag value 436 || scopedDependencyStack.isEmpty()) { 437 // TODO(beder): transitively check scopes of production components too. 438 rootComponentAnnotation(dependency, superficialValidation) 439 .filter(componentAnnotation -> !componentAnnotation.isProduction()) 440 .ifPresent( 441 componentAnnotation -> { 442 ImmutableSet<XTypeElement> scopedDependencies = 443 scopedTypesIn(componentAnnotation.dependencies()); 444 if (!scopedDependencies.isEmpty()) { 445 // empty can be ignored (base-case) 446 scopeStack.push(scopes); 447 scopedDependencyStack.push(dependency); 448 for (XTypeElement scopedDependency : scopedDependencies) { 449 validateDependencyScopeHierarchy( 450 component, scopedDependency, scopeStack, scopedDependencyStack); 451 } 452 scopedDependencyStack.pop(); 453 scopeStack.pop(); 454 } 455 }); // else: we skip component dependencies which are not components 456 } 457 } 458 stackOverlaps(Deque<ImmutableSet<T>> stack, ImmutableSet<T> set)459 private <T> boolean stackOverlaps(Deque<ImmutableSet<T>> stack, ImmutableSet<T> set) { 460 for (ImmutableSet<T> entry : stack) { 461 if (!Sets.intersection(entry, set).isEmpty()) { 462 return true; 463 } 464 } 465 return false; 466 } 467 468 /** Appends and formats a list of indented component types (with their scope annotations). */ appendIndentedComponentsList(StringBuilder message, Iterable<XTypeElement> types)469 private void appendIndentedComponentsList(StringBuilder message, Iterable<XTypeElement> types) { 470 for (XTypeElement scopedComponent : types) { 471 message.append(INDENT); 472 for (Scope scope : injectionAnnotations.getScopes(scopedComponent)) { 473 message.append(getReadableSource(scope)).append(' '); 474 } 475 message 476 .append(stripCommonTypePrefixes(scopedComponent.getQualifiedName().toString())) 477 .append('\n'); 478 } 479 } 480 481 /** 482 * Returns a set of type elements containing only those found in the input set that have a 483 * scoping annotation. 484 */ scopedTypesIn(Collection<XTypeElement> types)485 private ImmutableSet<XTypeElement> scopedTypesIn(Collection<XTypeElement> types) { 486 return types.stream() 487 .filter(type -> !injectionAnnotations.getScopes(type).isEmpty()) 488 .collect(toImmutableSet()); 489 } 490 } 491 } 492