1 /* 2 * Copyright (C) 2014 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.common.collect.Iterables.getOnlyElement; 20 import static dagger.internal.codegen.base.ComponentAnnotation.isComponentAnnotation; 21 import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation; 22 import static dagger.internal.codegen.base.ComponentCreatorAnnotation.getCreatorAnnotations; 23 import static dagger.internal.codegen.base.ModuleAnnotation.isModuleAnnotation; 24 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; 25 import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator; 26 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; 27 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 28 import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName; 29 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 30 import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; 31 import static dagger.internal.codegen.xprocessing.XTypeElements.hasTypeParameters; 32 import static dagger.internal.codegen.xprocessing.XTypeElements.isEffectivelyPrivate; 33 import static dagger.internal.codegen.xprocessing.XTypeElements.isEffectivelyPublic; 34 import static dagger.internal.codegen.xprocessing.XTypes.areEquivalentTypes; 35 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; 36 import static java.util.stream.Collectors.joining; 37 import static kotlin.streams.jdk8.StreamsKt.asStream; 38 39 import androidx.room.compiler.processing.XAnnotation; 40 import androidx.room.compiler.processing.XAnnotationValue; 41 import androidx.room.compiler.processing.XMethodElement; 42 import androidx.room.compiler.processing.XProcessingEnv; 43 import androidx.room.compiler.processing.XType; 44 import androidx.room.compiler.processing.XTypeElement; 45 import com.google.common.base.Joiner; 46 import com.google.common.collect.ImmutableList; 47 import com.google.common.collect.ImmutableListMultimap; 48 import com.google.common.collect.ImmutableSet; 49 import com.google.common.collect.ListMultimap; 50 import com.google.common.collect.MultimapBuilder; 51 import com.google.common.collect.Multimaps; 52 import com.google.common.collect.Sets; 53 import com.squareup.javapoet.ClassName; 54 import com.squareup.javapoet.TypeName; 55 import dagger.internal.codegen.base.ComponentCreatorAnnotation; 56 import dagger.internal.codegen.base.DaggerSuperficialValidation; 57 import dagger.internal.codegen.base.ModuleKind; 58 import dagger.internal.codegen.binding.BindingGraphFactory; 59 import dagger.internal.codegen.binding.ComponentDescriptorFactory; 60 import dagger.internal.codegen.binding.InjectionAnnotations; 61 import dagger.internal.codegen.binding.MethodSignatureFormatter; 62 import dagger.internal.codegen.javapoet.TypeNames; 63 import dagger.internal.codegen.xprocessing.XElements; 64 import dagger.spi.model.BindingGraph; 65 import dagger.spi.model.Scope; 66 import java.util.ArrayList; 67 import java.util.Collection; 68 import java.util.EnumSet; 69 import java.util.HashMap; 70 import java.util.HashSet; 71 import java.util.List; 72 import java.util.Map; 73 import java.util.Optional; 74 import java.util.Set; 75 import javax.inject.Inject; 76 import javax.inject.Singleton; 77 78 /** 79 * A {@linkplain ValidationReport validator} for {@link dagger.Module}s or {@link 80 * dagger.producers.ProducerModule}s. 81 */ 82 @Singleton 83 public final class ModuleValidator { 84 private static final ImmutableSet<ClassName> SUBCOMPONENT_TYPES = 85 ImmutableSet.of(TypeNames.SUBCOMPONENT, TypeNames.PRODUCTION_SUBCOMPONENT); 86 private static final ImmutableSet<ClassName> SUBCOMPONENT_CREATOR_TYPES = 87 ImmutableSet.of( 88 TypeNames.SUBCOMPONENT_BUILDER, 89 TypeNames.SUBCOMPONENT_FACTORY, 90 TypeNames.PRODUCTION_SUBCOMPONENT_BUILDER, 91 TypeNames.PRODUCTION_SUBCOMPONENT_FACTORY); 92 private static final Optional<Class<?>> ANDROID_PROCESSOR; 93 private static final String CONTRIBUTES_ANDROID_INJECTOR_NAME = 94 "dagger.android.ContributesAndroidInjector"; 95 private static final String ANDROID_PROCESSOR_NAME = "dagger.android.processor.AndroidProcessor"; 96 97 static { 98 Class<?> clazz; 99 try { 100 clazz = Class.forName(ANDROID_PROCESSOR_NAME, false, ModuleValidator.class.getClassLoader()); 101 } catch (ClassNotFoundException ignored) { 102 clazz = null; 103 } 104 ANDROID_PROCESSOR = Optional.ofNullable(clazz); 105 } 106 107 private final AnyBindingMethodValidator anyBindingMethodValidator; 108 private final MethodSignatureFormatter methodSignatureFormatter; 109 private final ComponentDescriptorFactory componentDescriptorFactory; 110 private final BindingGraphFactory bindingGraphFactory; 111 private final BindingGraphValidator bindingGraphValidator; 112 private final InjectionAnnotations injectionAnnotations; 113 private final DaggerSuperficialValidation superficialValidation; 114 private final XProcessingEnv processingEnv; 115 private final Map<XTypeElement, ValidationReport> cache = new HashMap<>(); 116 private final Set<XTypeElement> knownModules = new HashSet<>(); 117 118 @Inject ModuleValidator( AnyBindingMethodValidator anyBindingMethodValidator, MethodSignatureFormatter methodSignatureFormatter, ComponentDescriptorFactory componentDescriptorFactory, BindingGraphFactory bindingGraphFactory, BindingGraphValidator bindingGraphValidator, InjectionAnnotations injectionAnnotations, DaggerSuperficialValidation superficialValidation, XProcessingEnv processingEnv)119 ModuleValidator( 120 AnyBindingMethodValidator anyBindingMethodValidator, 121 MethodSignatureFormatter methodSignatureFormatter, 122 ComponentDescriptorFactory componentDescriptorFactory, 123 BindingGraphFactory bindingGraphFactory, 124 BindingGraphValidator bindingGraphValidator, 125 InjectionAnnotations injectionAnnotations, 126 DaggerSuperficialValidation superficialValidation, 127 XProcessingEnv processingEnv) { 128 this.anyBindingMethodValidator = anyBindingMethodValidator; 129 this.methodSignatureFormatter = methodSignatureFormatter; 130 this.componentDescriptorFactory = componentDescriptorFactory; 131 this.bindingGraphFactory = bindingGraphFactory; 132 this.bindingGraphValidator = bindingGraphValidator; 133 this.injectionAnnotations = injectionAnnotations; 134 this.superficialValidation = superficialValidation; 135 this.processingEnv = processingEnv; 136 } 137 138 /** 139 * Adds {@code modules} to the set of module types that will be validated during this compilation 140 * step. If a component or module includes a module that is not in this set, that included module 141 * is assumed to be valid because it was processed in a previous compilation step. If it were 142 * invalid, that previous compilation step would have failed and blocked this one. 143 * 144 * <p>This logic depends on this method being called before {@linkplain #validate(XTypeElement) 145 * validating} any module or {@linkplain #validateReferencedModules(XTypeElement, ModuleKind, Set, 146 * DiagnosticReporter.Builder) component}. 147 */ addKnownModules(Collection<XTypeElement> modules)148 public void addKnownModules(Collection<XTypeElement> modules) { 149 knownModules.addAll(modules); 150 } 151 152 /** Returns a validation report for a module type. */ validate(XTypeElement module)153 public ValidationReport validate(XTypeElement module) { 154 return validate(module, new HashSet<>()); 155 } 156 validate(XTypeElement module, Set<XTypeElement> visitedModules)157 private ValidationReport validate(XTypeElement module, Set<XTypeElement> visitedModules) { 158 if (visitedModules.add(module)) { 159 return reentrantComputeIfAbsent(cache, module, m -> validateUncached(module, visitedModules)); 160 } 161 return ValidationReport.about(module).build(); 162 } 163 validateUncached(XTypeElement module, Set<XTypeElement> visitedModules)164 private ValidationReport validateUncached(XTypeElement module, Set<XTypeElement> visitedModules) { 165 ValidationReport.Builder builder = ValidationReport.about(module); 166 ModuleKind moduleKind = ModuleKind.forAnnotatedElement(module).get(); 167 Optional<XType> contributesAndroidInjector = 168 Optional.ofNullable(processingEnv.findTypeElement(CONTRIBUTES_ANDROID_INJECTOR_NAME)) 169 .map(XTypeElement::getType); 170 List<XMethodElement> moduleMethods = module.getDeclaredMethods(); 171 List<XMethodElement> bindingMethods = new ArrayList<>(); 172 for (XMethodElement moduleMethod : moduleMethods) { 173 if (anyBindingMethodValidator.isBindingMethod(moduleMethod)) { 174 builder.addSubreport(anyBindingMethodValidator.validate(moduleMethod)); 175 bindingMethods.add(moduleMethod); 176 } 177 178 for (XAnnotation annotation : moduleMethod.getAllAnnotations()) { 179 if (!ANDROID_PROCESSOR.isPresent() 180 && contributesAndroidInjector.isPresent() 181 && areEquivalentTypes(contributesAndroidInjector.get(), annotation.getType())) { 182 builder.addSubreport( 183 ValidationReport.about(moduleMethod) 184 .addError( 185 String.format( 186 "@%s was used, but %s was not found on the processor path", 187 CONTRIBUTES_ANDROID_INJECTOR_NAME, ANDROID_PROCESSOR_NAME)) 188 .build()); 189 break; 190 } 191 } 192 } 193 194 if (bindingMethods.stream() 195 .map(ModuleMethodKind::ofMethod) 196 .collect(toImmutableSet()) 197 .containsAll( 198 EnumSet.of(ModuleMethodKind.ABSTRACT_DECLARATION, ModuleMethodKind.INSTANCE_BINDING))) { 199 builder.addError( 200 String.format( 201 "A @%s may not contain both non-static and abstract binding methods", 202 moduleKind.annotation().simpleName())); 203 } 204 205 validateModuleVisibility(module, moduleKind, builder); 206 207 ImmutableListMultimap<String, XMethodElement> bindingMethodsByName = 208 Multimaps.index(bindingMethods, XElements::getSimpleName); 209 210 validateMethodsWithSameName(builder, bindingMethodsByName); 211 if (!module.isInterface()) { 212 validateBindingMethodOverrides( 213 module, 214 builder, 215 Multimaps.index(moduleMethods, XElements::getSimpleName), 216 bindingMethodsByName); 217 } 218 validateModifiers(module, builder); 219 validateReferencedModules(module, moduleKind, visitedModules, builder); 220 validateReferencedSubcomponents(module, moduleKind, builder); 221 validateNoScopeAnnotationsOnModuleElement(module, moduleKind, builder); 222 validateSelfCycles(module, moduleKind, builder); 223 module.getEnclosedTypeElements().stream() 224 .filter(XTypeElement::isCompanionObject) 225 .collect(toOptional()) 226 .ifPresent(companionModule -> validateCompanionModule(companionModule, builder)); 227 228 if (builder.build().isClean() 229 && bindingGraphValidator.shouldDoFullBindingGraphValidation(module)) { 230 validateModuleBindings(module, builder); 231 } 232 233 return builder.build(); 234 } 235 validateReferencedSubcomponents( XTypeElement subject, ModuleKind moduleKind, ValidationReport.Builder builder)236 private void validateReferencedSubcomponents( 237 XTypeElement subject, ModuleKind moduleKind, ValidationReport.Builder builder) { 238 XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(subject); 239 for (XAnnotationValue subcomponentValue : 240 moduleAnnotation.getAsAnnotationValueList("subcomponents")) { 241 XType type = subcomponentValue.asType(); 242 if (!isDeclared(type)) { 243 builder.addError( 244 type + " is not a valid subcomponent type", 245 subject, 246 moduleAnnotation, 247 subcomponentValue); 248 continue; 249 } 250 251 XTypeElement subcomponentElement = type.getTypeElement(); 252 if (hasAnyAnnotation(subcomponentElement, SUBCOMPONENT_TYPES)) { 253 validateSubcomponentHasBuilder(subject, subcomponentElement, moduleAnnotation, builder); 254 } else { 255 builder.addError( 256 hasAnyAnnotation(subcomponentElement, SUBCOMPONENT_CREATOR_TYPES) 257 ? moduleSubcomponentsIncludesCreator(subcomponentElement) 258 : moduleSubcomponentsIncludesNonSubcomponent(subcomponentElement), 259 subject, 260 moduleAnnotation, 261 subcomponentValue); 262 } 263 } 264 } 265 moduleSubcomponentsIncludesNonSubcomponent(XTypeElement notSubcomponent)266 private static String moduleSubcomponentsIncludesNonSubcomponent(XTypeElement notSubcomponent) { 267 return notSubcomponent.getQualifiedName() 268 + " is not a @Subcomponent or @ProductionSubcomponent"; 269 } 270 moduleSubcomponentsIncludesCreator(XTypeElement moduleSubcomponentsAttribute)271 private String moduleSubcomponentsIncludesCreator(XTypeElement moduleSubcomponentsAttribute) { 272 XTypeElement subcomponentType = moduleSubcomponentsAttribute.getEnclosingTypeElement(); 273 ComponentCreatorAnnotation creatorAnnotation = 274 getOnlyElement(getCreatorAnnotations(moduleSubcomponentsAttribute)); 275 return String.format( 276 "%s is a @%s.%s. Did you mean to use %s?", 277 moduleSubcomponentsAttribute.getQualifiedName(), 278 subcomponentAnnotation(subcomponentType, superficialValidation).get().simpleName(), 279 creatorAnnotation.creatorKind().typeName(), 280 subcomponentType.getQualifiedName()); 281 } 282 validateSubcomponentHasBuilder( XTypeElement subject, XTypeElement subcomponentAttribute, XAnnotation moduleAnnotation, ValidationReport.Builder builder)283 private void validateSubcomponentHasBuilder( 284 XTypeElement subject, 285 XTypeElement subcomponentAttribute, 286 XAnnotation moduleAnnotation, 287 ValidationReport.Builder builder) { 288 if (getSubcomponentCreator(subcomponentAttribute).isPresent()) { 289 return; 290 } 291 builder.addError( 292 moduleSubcomponentsDoesntHaveCreator(subcomponentAttribute, moduleAnnotation), 293 subject, 294 moduleAnnotation); 295 } 296 moduleSubcomponentsDoesntHaveCreator( XTypeElement subcomponent, XAnnotation moduleAnnotation)297 private String moduleSubcomponentsDoesntHaveCreator( 298 XTypeElement subcomponent, XAnnotation moduleAnnotation) { 299 return String.format( 300 "%1$s doesn't have a @%2$s.Builder or @%2$s.Factory, which is required when used with " 301 + "@%3$s.subcomponents", 302 subcomponent.getQualifiedName(), 303 subcomponentAnnotation(subcomponent, superficialValidation).get().simpleName(), 304 getClassName(moduleAnnotation).simpleName()); 305 } 306 307 enum ModuleMethodKind { 308 ABSTRACT_DECLARATION, 309 INSTANCE_BINDING, 310 STATIC_BINDING, 311 ; 312 ofMethod(XMethodElement moduleMethod)313 static ModuleMethodKind ofMethod(XMethodElement moduleMethod) { 314 if (moduleMethod.isStatic()) { 315 return STATIC_BINDING; 316 } else if (moduleMethod.isAbstract()) { 317 return ABSTRACT_DECLARATION; 318 } else { 319 return INSTANCE_BINDING; 320 } 321 } 322 } 323 validateModifiers(XTypeElement subject, ValidationReport.Builder builder)324 private void validateModifiers(XTypeElement subject, ValidationReport.Builder builder) { 325 // This coupled with the check for abstract modules in ComponentValidator guarantees that 326 // only modules without type parameters are referenced from @Component(modules={...}). 327 if (hasTypeParameters(subject) && !subject.isAbstract()) { 328 builder.addError("Modules with type parameters must be abstract", subject); 329 } 330 } 331 validateMethodsWithSameName( ValidationReport.Builder builder, ListMultimap<String, XMethodElement> bindingMethodsByName)332 private void validateMethodsWithSameName( 333 ValidationReport.Builder builder, ListMultimap<String, XMethodElement> bindingMethodsByName) { 334 bindingMethodsByName.asMap().values().stream() 335 .filter(methods -> methods.size() > 1) 336 .flatMap(Collection::stream) 337 .forEach( 338 duplicateMethod -> { 339 builder.addError( 340 "Cannot have more than one binding method with the same name in a single module", 341 duplicateMethod); 342 }); 343 } 344 validateReferencedModules( XTypeElement subject, ModuleKind moduleKind, Set<XTypeElement> visitedModules, ValidationReport.Builder builder)345 private void validateReferencedModules( 346 XTypeElement subject, 347 ModuleKind moduleKind, 348 Set<XTypeElement> visitedModules, 349 ValidationReport.Builder builder) { 350 // Validate that all the modules we include are valid for inclusion. 351 XAnnotation mirror = moduleKind.getModuleAnnotation(subject); 352 builder.addSubreport( 353 validateReferencedModules( 354 subject, mirror, moduleKind.legalIncludedModuleKinds(), visitedModules)); 355 } 356 357 /** 358 * Validates modules included in a given module or installed in a given component. 359 * 360 * <p>Checks that the referenced modules are non-generic types annotated with {@code @Module} or 361 * {@code @ProducerModule}. 362 * 363 * <p>If the referenced module is in the {@linkplain #addKnownModules(Collection) known modules 364 * set} and has errors, reports an error at that module's inclusion. 365 * 366 * @param annotatedType the annotated module or component 367 * @param annotation the annotation specifying the referenced modules ({@code @Component}, 368 * {@code @ProductionComponent}, {@code @Subcomponent}, {@code @ProductionSubcomponent}, 369 * {@code @Module}, or {@code @ProducerModule}) 370 * @param validModuleKinds the module kinds that the annotated type is permitted to include 371 */ validateReferencedModules( XTypeElement annotatedType, XAnnotation annotation, ImmutableSet<ModuleKind> validModuleKinds, Set<XTypeElement> visitedModules)372 ValidationReport validateReferencedModules( 373 XTypeElement annotatedType, 374 XAnnotation annotation, 375 ImmutableSet<ModuleKind> validModuleKinds, 376 Set<XTypeElement> visitedModules) { 377 superficialValidation.validateAnnotationOf(annotatedType, annotation); 378 379 ValidationReport.Builder subreport = ValidationReport.about(annotatedType); 380 // TODO(bcorso): Consider creating a DiagnosticLocation object to encapsulate the location in a 381 // single object to avoid duplication across all reported errors 382 for (XAnnotationValue includedModule : getModules(annotation)) { 383 XType type = includedModule.asType(); 384 if (!isDeclared(type)) { 385 subreport.addError( 386 String.format("%s is not a valid module type.", type), 387 annotatedType, 388 annotation, 389 includedModule); 390 continue; 391 } 392 393 XTypeElement module = type.getTypeElement(); 394 if (hasTypeParameters(module)) { 395 subreport.addError( 396 String.format( 397 "%s is listed as a module, but has type parameters", module.getQualifiedName()), 398 annotatedType, 399 annotation, 400 includedModule); 401 } 402 403 ImmutableSet<ClassName> validModuleAnnotations = 404 validModuleKinds.stream().map(ModuleKind::annotation).collect(toImmutableSet()); 405 if (!hasAnyAnnotation(module, validModuleAnnotations)) { 406 subreport.addError( 407 String.format( 408 "%s is listed as a module, but is not annotated with %s", 409 module.getQualifiedName(), 410 (validModuleAnnotations.size() > 1 ? "one of " : "") 411 + validModuleAnnotations.stream() 412 .map(otherClass -> "@" + otherClass.simpleName()) 413 .collect(joining(", "))), 414 annotatedType, 415 annotation, 416 includedModule); 417 } else if (knownModules.contains(module) && !validate(module, visitedModules).isClean()) { 418 subreport.addError( 419 String.format("%s has errors", module.getQualifiedName()), 420 annotatedType, 421 annotation, 422 includedModule); 423 } 424 if (module.isCompanionObject()) { 425 subreport.addError( 426 String.format( 427 "%s is listed as a module, but it is a companion object class. " 428 + "Add @Module to the enclosing class and reference that instead.", 429 module.getQualifiedName()), 430 annotatedType, 431 annotation, 432 includedModule); 433 } 434 } 435 return subreport.build(); 436 } 437 getModules(XAnnotation annotation)438 private static ImmutableList<XAnnotationValue> getModules(XAnnotation annotation) { 439 if (isModuleAnnotation(annotation)) { 440 return ImmutableList.copyOf(annotation.getAsAnnotationValueList("includes")); 441 } 442 if (isComponentAnnotation(annotation)) { 443 return ImmutableList.copyOf(annotation.getAsAnnotationValueList("modules")); 444 } 445 throw new IllegalArgumentException(String.format("unsupported annotation: %s", annotation)); 446 } 447 validateBindingMethodOverrides( XTypeElement subject, ValidationReport.Builder builder, ImmutableListMultimap<String, XMethodElement> moduleMethodsByName, ImmutableListMultimap<String, XMethodElement> bindingMethodsByName)448 private void validateBindingMethodOverrides( 449 XTypeElement subject, 450 ValidationReport.Builder builder, 451 ImmutableListMultimap<String, XMethodElement> moduleMethodsByName, 452 ImmutableListMultimap<String, XMethodElement> bindingMethodsByName) { 453 // For every binding method, confirm it overrides nothing *and* nothing overrides it. 454 // Consider the following hierarchy: 455 // class Parent { 456 // @Provides Foo a() {} 457 // @Provides Foo b() {} 458 // Foo c() {} 459 // } 460 // class Child extends Parent { 461 // @Provides Foo a() {} 462 // Foo b() {} 463 // @Provides Foo c() {} 464 // } 465 // In each of those cases, we want to fail. "a" is clear, "b" because Child is overriding 466 // a binding method in Parent, and "c" because Child is defining a binding method that overrides 467 // Parent. 468 XTypeElement currentClass = subject; 469 XType objectType = processingEnv.findType(TypeName.OBJECT); 470 // We keep track of visited methods so we don't spam with multiple failures. 471 Set<XMethodElement> visitedMethods = Sets.newHashSet(); 472 ListMultimap<String, XMethodElement> allMethodsByName = 473 MultimapBuilder.hashKeys().arrayListValues().build(moduleMethodsByName); 474 475 while (!currentClass.getSuperType().isSameType(objectType)) { 476 currentClass = currentClass.getSuperType().getTypeElement(); 477 List<XMethodElement> superclassMethods = currentClass.getDeclaredMethods(); 478 for (XMethodElement superclassMethod : superclassMethods) { 479 String name = getSimpleName(superclassMethod); 480 // For each method in the superclass, confirm our binding methods don't override it 481 for (XMethodElement bindingMethod : bindingMethodsByName.get(name)) { 482 if (visitedMethods.add(bindingMethod) 483 && bindingMethod.overrides(superclassMethod, subject)) { 484 builder.addError( 485 String.format( 486 "Binding methods may not override another method. Overrides: %s", 487 methodSignatureFormatter.format(superclassMethod)), 488 bindingMethod); 489 } 490 } 491 // For each binding method in superclass, confirm our methods don't override it. 492 if (anyBindingMethodValidator.isBindingMethod(superclassMethod)) { 493 for (XMethodElement method : allMethodsByName.get(name)) { 494 if (visitedMethods.add(method) && method.overrides(superclassMethod, subject)) { 495 builder.addError( 496 String.format( 497 "Binding methods may not be overridden in modules. Overrides: %s", 498 methodSignatureFormatter.format(superclassMethod)), 499 method); 500 } 501 } 502 } 503 // TODO(b/202521399): Add a test for cases that add to this map. 504 allMethodsByName.put(getSimpleName(superclassMethod), superclassMethod); 505 } 506 } 507 } 508 validateModuleVisibility( XTypeElement moduleElement, ModuleKind moduleKind, ValidationReport.Builder reportBuilder)509 private void validateModuleVisibility( 510 XTypeElement moduleElement, ModuleKind moduleKind, ValidationReport.Builder reportBuilder) { 511 if (moduleElement.isPrivate()) { 512 reportBuilder.addError("Modules cannot be private.", moduleElement); 513 } else if (isEffectivelyPrivate(moduleElement)) { 514 reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement); 515 } 516 if (isEffectivelyPublic(moduleElement)) { 517 ImmutableSet<XTypeElement> invalidVisibilityIncludes = 518 getModuleIncludesWithInvalidVisibility(moduleKind.getModuleAnnotation(moduleElement)); 519 if (!invalidVisibilityIncludes.isEmpty()) { 520 reportBuilder.addError( 521 String.format( 522 "This module is public, but it includes non-public (or effectively non-public) " 523 + "modules (%s) that have non-static, non-abstract binding methods. Either " 524 + "reduce the visibility of this module, make the included modules " 525 + "public, or make all of the binding methods on the included modules " 526 + "abstract or static.", 527 formatListForErrorMessage(invalidVisibilityIncludes.asList())), 528 moduleElement); 529 } 530 } 531 } 532 getModuleIncludesWithInvalidVisibility( XAnnotation moduleAnnotation)533 private ImmutableSet<XTypeElement> getModuleIncludesWithInvalidVisibility( 534 XAnnotation moduleAnnotation) { 535 return moduleAnnotation.getAnnotationValue("includes").asTypeList().stream() 536 .map(XType::getTypeElement) 537 .filter(include -> !isEffectivelyPublic(include)) 538 .filter(this::requiresModuleInstance) 539 .collect(toImmutableSet()); 540 } 541 542 /** 543 * Returns {@code true} if a module instance is needed for any of the binding methods on the given 544 * {@code module}. This is the case when the module has any binding methods that are neither 545 * {@code abstract} nor {@code static}. Alternatively, if the module is a Kotlin Object then the 546 * binding methods are considered {@code static}, requiring no module instance. 547 */ requiresModuleInstance(XTypeElement module)548 private boolean requiresModuleInstance(XTypeElement module) { 549 // Note: We use XTypeElement#getAllMethods() rather than XTypeElement#getDeclaredMethods() here 550 // because we need to include binding methods declared in supertypes because unlike most other 551 // validations being done in this class, which assume that supertype binding methods will be 552 // validated in a separate call to the validator since the supertype itself must be a @Module, 553 // we need to look at all the binding methods in the module's type hierarchy here. 554 return !(module.isKotlinObject() || module.isCompanionObject()) 555 && !asStream(module.getAllMethods()) 556 .filter(anyBindingMethodValidator::isBindingMethod) 557 .allMatch(method -> method.isAbstract() || method.isStatic()); 558 } 559 validateNoScopeAnnotationsOnModuleElement( XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder report)560 private void validateNoScopeAnnotationsOnModuleElement( 561 XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder report) { 562 for (Scope scope : injectionAnnotations.getScopes(module)) { 563 report.addError( 564 String.format( 565 "@%ss cannot be scoped. Did you mean to scope a method instead?", 566 moduleKind.annotation().simpleName()), 567 module, 568 scope.scopeAnnotation().xprocessing()); 569 } 570 } 571 validateSelfCycles( XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder builder)572 private void validateSelfCycles( 573 XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder builder) { 574 XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(module); 575 moduleAnnotation.getAsAnnotationValueList("includes").stream() 576 .filter(includedModule -> areEquivalentTypes(module.getType(), includedModule.asType())) 577 .forEach( 578 includedModule -> 579 builder.addError( 580 String.format( 581 "@%s cannot include themselves.", moduleKind.annotation().simpleName()), 582 module, 583 moduleAnnotation, 584 includedModule)); 585 } 586 validateCompanionModule( XTypeElement companionModule, ValidationReport.Builder builder)587 private void validateCompanionModule( 588 XTypeElement companionModule, ValidationReport.Builder builder) { 589 List<XMethodElement> companionBindingMethods = new ArrayList<>(); 590 for (XMethodElement companionMethod : companionModule.getDeclaredMethods()) { 591 if (anyBindingMethodValidator.isBindingMethod(companionMethod)) { 592 builder.addSubreport(anyBindingMethodValidator.validate(companionMethod)); 593 companionBindingMethods.add(companionMethod); 594 } 595 596 // On normal modules only overriding other binding methods is disallowed, but for companion 597 // objects we are prohibiting any override. For this can rely on checking the @Override 598 // annotation since the Kotlin compiler will always produce them for overriding methods. 599 if (companionMethod.hasAnnotation(TypeNames.OVERRIDE)) { 600 builder.addError( 601 "Binding method in companion object may not override another method.", companionMethod); 602 } 603 604 // TODO(danysantiago): Be strict about the usage of @JvmStatic, i.e. tell user to remove it. 605 } 606 607 ImmutableListMultimap<String, XMethodElement> bindingMethodsByName = 608 Multimaps.index(companionBindingMethods, XElements::getSimpleName); 609 validateMethodsWithSameName(builder, bindingMethodsByName); 610 611 // If there are provision methods, then check the visibility. Companion objects are composed by 612 // an inner class and a static field, it is not enough to check the visibility on the type 613 // element or the field, therefore we check the metadata. 614 if (!companionBindingMethods.isEmpty() && companionModule.isPrivate()) { 615 builder.addError( 616 "A Companion Module with binding methods cannot be private.", companionModule); 617 } 618 } 619 validateModuleBindings(XTypeElement module, ValidationReport.Builder report)620 private void validateModuleBindings(XTypeElement module, ValidationReport.Builder report) { 621 BindingGraph bindingGraph = 622 bindingGraphFactory 623 .create(componentDescriptorFactory.moduleComponentDescriptor(module), true) 624 .topLevelBindingGraph(); 625 if (!bindingGraphValidator.isValid(bindingGraph)) { 626 // Since the validator uses a DiagnosticReporter to report errors, the ValdiationReport won't 627 // have any Items for them. We have to tell the ValidationReport that some errors were 628 // reported for the subject. 629 report.markDirty(); 630 } 631 } 632 formatListForErrorMessage(List<?> things)633 private static String formatListForErrorMessage(List<?> things) { 634 switch (things.size()) { 635 case 0: 636 return ""; 637 case 1: 638 return things.get(0).toString(); 639 default: 640 StringBuilder output = new StringBuilder(); 641 Joiner.on(", ").appendTo(output, things.subList(0, things.size() - 1)); 642 output.append(" and ").append(things.get(things.size() - 1)); 643 return output.toString(); 644 } 645 } 646 } 647