1 /* 2 * Copyright (C) 2015 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.XTypeKt.isVoid; 20 import static com.google.common.collect.Iterables.getOnlyElement; 21 import static dagger.internal.codegen.base.ComponentCreatorAnnotation.getCreatorAnnotations; 22 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; 23 import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters; 24 import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; 25 import static dagger.internal.codegen.xprocessing.XTypeElements.hasTypeParameters; 26 import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; 27 import static dagger.internal.codegen.xprocessing.XTypes.isSubtype; 28 import static javax.lang.model.SourceVersion.isKeyword; 29 30 import androidx.room.compiler.processing.XConstructorElement; 31 import androidx.room.compiler.processing.XExecutableParameterElement; 32 import androidx.room.compiler.processing.XMethodElement; 33 import androidx.room.compiler.processing.XType; 34 import androidx.room.compiler.processing.XTypeElement; 35 import com.google.common.collect.ImmutableList; 36 import com.google.common.collect.ImmutableSet; 37 import com.google.common.collect.ObjectArrays; 38 import dagger.internal.codegen.base.ClearableCache; 39 import dagger.internal.codegen.base.ComponentCreatorAnnotation; 40 import dagger.internal.codegen.binding.ErrorMessages; 41 import dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages; 42 import dagger.internal.codegen.binding.MethodSignatureFormatter; 43 import dagger.internal.codegen.javapoet.TypeNames; 44 import dagger.internal.codegen.kotlin.KotlinMetadataUtil; 45 import java.util.HashMap; 46 import java.util.List; 47 import java.util.Map; 48 import javax.inject.Inject; 49 import javax.inject.Singleton; 50 51 /** Validates types annotated with component creator annotations. */ 52 @Singleton 53 public final class ComponentCreatorValidator implements ClearableCache { 54 55 private final Map<XTypeElement, ValidationReport> reports = new HashMap<>(); 56 private final MethodSignatureFormatter methodSignatureFormatter; 57 private final KotlinMetadataUtil metadataUtil; 58 59 @Inject ComponentCreatorValidator( MethodSignatureFormatter methodSignatureFormatter, KotlinMetadataUtil metadataUtil)60 ComponentCreatorValidator( 61 MethodSignatureFormatter methodSignatureFormatter, KotlinMetadataUtil metadataUtil) { 62 this.methodSignatureFormatter = methodSignatureFormatter; 63 this.metadataUtil = metadataUtil; 64 } 65 66 @Override clearCache()67 public void clearCache() { 68 reports.clear(); 69 } 70 71 /** Validates that the given {@code type} is potentially a valid component creator type. */ validate(XTypeElement type)72 public ValidationReport validate(XTypeElement type) { 73 return reentrantComputeIfAbsent(reports, type, this::validateUncached); 74 } 75 validateUncached(XTypeElement type)76 private ValidationReport validateUncached(XTypeElement type) { 77 ValidationReport.Builder report = ValidationReport.about(type); 78 79 ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations = getCreatorAnnotations(type); 80 if (!validateOnlyOneCreatorAnnotation(creatorAnnotations, report)) { 81 return report.build(); 82 } 83 84 // Note: there's more validation in ComponentDescriptorValidator: 85 // - to make sure the setter methods/factory parameters mirror the deps 86 // - to make sure each type or key is set by only one method or parameter 87 ElementValidator validator = 88 new ElementValidator(type, report, getOnlyElement(creatorAnnotations)); 89 return validator.validate(); 90 } 91 validateOnlyOneCreatorAnnotation( ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations, ValidationReport.Builder report)92 private boolean validateOnlyOneCreatorAnnotation( 93 ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations, 94 ValidationReport.Builder report) { 95 // creatorAnnotations should never be empty because this should only ever be called for 96 // types that have been found to have some creator annotation 97 if (creatorAnnotations.size() > 1) { 98 String error = 99 "May not have more than one component Factory or Builder annotation on a type" 100 + ": found " 101 + creatorAnnotations; 102 report.addError(error); 103 return false; 104 } 105 106 return true; 107 } 108 109 /** 110 * Validator for a single {@link XTypeElement} that is annotated with a {@code Builder} or {@code 111 * Factory} annotation. 112 */ 113 private final class ElementValidator { 114 private final XTypeElement creator; 115 private final ValidationReport.Builder report; 116 private final ComponentCreatorAnnotation annotation; 117 private final ComponentCreatorMessages messages; 118 ElementValidator( XTypeElement creator, ValidationReport.Builder report, ComponentCreatorAnnotation annotation)119 private ElementValidator( 120 XTypeElement creator, 121 ValidationReport.Builder report, 122 ComponentCreatorAnnotation annotation) { 123 this.creator = creator; 124 this.report = report; 125 this.annotation = annotation; 126 this.messages = ErrorMessages.creatorMessagesFor(annotation); 127 } 128 129 /** Validates the creator type. */ validate()130 final ValidationReport validate() { 131 XTypeElement enclosingType = creator.getEnclosingTypeElement(); 132 133 // If the type isn't enclosed in a component don't validate anything else since the rest of 134 // the messages will be bogus. 135 if (enclosingType == null 136 || !enclosingType.hasAnnotation(annotation.componentAnnotation())) { 137 return report.addError(messages.mustBeInComponent()).build(); 138 } 139 140 // If the type isn't a class or interface, don't validate anything else since the rest of the 141 // messages will be bogus. 142 if (!validateIsClassOrInterface()) { 143 return report.build(); 144 } 145 146 // If the type isn't a valid creator type, don't validate anything else since the rest of the 147 // messages will be bogus. 148 if (!validateTypeRequirements()) { 149 return report.build(); 150 } 151 152 switch (annotation.creatorKind()) { 153 case FACTORY: 154 validateFactory(); 155 break; 156 case BUILDER: 157 validateBuilder(); 158 } 159 160 return report.build(); 161 } 162 163 /** Validates that the type is a class or interface type and returns true if it is. */ validateIsClassOrInterface()164 private boolean validateIsClassOrInterface() { 165 if (creator.isClass()) { 166 validateConstructor(); 167 return true; 168 } 169 if (creator.isInterface()) { 170 return true; 171 } 172 report.addError(messages.mustBeClassOrInterface()); 173 return false; 174 } 175 validateConstructor()176 private void validateConstructor() { 177 List<XConstructorElement> constructors = creator.getConstructors(); 178 179 boolean valid = true; 180 if (constructors.size() != 1) { 181 valid = false; 182 } else { 183 XConstructorElement constructor = getOnlyElement(constructors); 184 valid = constructor.getParameters().isEmpty() && !constructor.isPrivate(); 185 } 186 187 if (!valid) { 188 report.addError(messages.invalidConstructor()); 189 } 190 } 191 192 /** Validates basic requirements about the type that are common to both creator kinds. */ validateTypeRequirements()193 private boolean validateTypeRequirements() { 194 boolean isClean = true; 195 if (hasTypeParameters(creator)) { 196 report.addError(messages.generics()); 197 isClean = false; 198 } 199 if (creator.isPrivate()) { 200 report.addError(messages.isPrivate()); 201 isClean = false; 202 } 203 if (!creator.isStatic()) { 204 report.addError(messages.mustBeStatic()); 205 isClean = false; 206 } 207 // Note: Must be abstract, so no need to check for final. 208 if (!creator.isAbstract()) { 209 report.addError(messages.mustBeAbstract()); 210 isClean = false; 211 } 212 return isClean; 213 } 214 validateBuilder()215 private void validateBuilder() { 216 validateClassMethodName(); 217 XMethodElement buildMethod = null; 218 for (XMethodElement method : getAllUnimplementedMethods(creator)) { 219 switch (method.getParameters().size()) { 220 case 0: // If this is potentially a build() method, validate it returns the correct type. 221 if (validateFactoryMethodReturnType(method)) { 222 if (buildMethod != null) { 223 // If we found more than one build-like method, fail. 224 error( 225 method, 226 messages.twoFactoryMethods(), 227 messages.inheritedTwoFactoryMethods(), 228 methodSignatureFormatter.format(buildMethod)); 229 } 230 } 231 // We set the buildMethod regardless of the return type to reduce error spam. 232 buildMethod = method; 233 break; 234 235 case 1: // If this correctly had one parameter, make sure the return types are valid. 236 validateSetterMethod(method); 237 break; 238 239 default: // more than one parameter 240 error( 241 method, 242 messages.setterMethodsMustTakeOneArg(), 243 messages.inheritedSetterMethodsMustTakeOneArg()); 244 break; 245 } 246 } 247 248 if (buildMethod == null) { 249 report.addError(messages.missingFactoryMethod()); 250 } else { 251 validateNotGeneric(buildMethod); 252 } 253 } 254 validateClassMethodName()255 private void validateClassMethodName() { 256 // Only Kotlin class can have method name the same as a Java reserved keyword, so only check 257 // the method name if this class is a Kotlin class. 258 if (metadataUtil.hasMetadata(creator)) { 259 metadataUtil 260 .getAllMethodNamesBySignature(creator) 261 .forEach( 262 (signature, name) -> { 263 if (isKeyword(name)) { 264 report.addError("Can not use a Java keyword as method name: " + signature); 265 } 266 }); 267 } 268 } 269 validateSetterMethod(XMethodElement method)270 private void validateSetterMethod(XMethodElement method) { 271 XType returnType = method.asMemberOf(creator.getType()).getReturnType(); 272 if (!isVoid(returnType) && !isSubtype(creator.getType(), returnType)) { 273 error( 274 method, 275 messages.setterMethodsMustReturnVoidOrBuilder(), 276 messages.inheritedSetterMethodsMustReturnVoidOrBuilder()); 277 } 278 279 validateNotGeneric(method); 280 281 XExecutableParameterElement parameter = method.getParameters().get(0); 282 283 boolean methodIsBindsInstance = method.hasAnnotation(TypeNames.BINDS_INSTANCE); 284 boolean parameterIsBindsInstance = parameter.hasAnnotation(TypeNames.BINDS_INSTANCE); 285 boolean bindsInstance = methodIsBindsInstance || parameterIsBindsInstance; 286 287 if (methodIsBindsInstance && parameterIsBindsInstance) { 288 error( 289 method, 290 messages.bindsInstanceNotAllowedOnBothSetterMethodAndParameter(), 291 messages.inheritedBindsInstanceNotAllowedOnBothSetterMethodAndParameter()); 292 } 293 294 if (!bindsInstance && isPrimitive(parameter.getType())) { 295 error( 296 method, 297 messages.nonBindsInstanceParametersMayNotBePrimitives(), 298 messages.inheritedNonBindsInstanceParametersMayNotBePrimitives()); 299 } 300 } 301 validateFactory()302 private void validateFactory() { 303 ImmutableList<XMethodElement> abstractMethods = getAllUnimplementedMethods(creator); 304 switch (abstractMethods.size()) { 305 case 0: 306 report.addError(messages.missingFactoryMethod()); 307 return; 308 case 1: 309 break; // good 310 default: 311 error( 312 abstractMethods.get(1), 313 messages.twoFactoryMethods(), 314 messages.inheritedTwoFactoryMethods(), 315 methodSignatureFormatter.format(abstractMethods.get(0))); 316 return; 317 } 318 319 validateFactoryMethod(getOnlyElement(abstractMethods)); 320 } 321 322 /** Validates that the given {@code method} is a valid component factory method. */ validateFactoryMethod(XMethodElement method)323 private void validateFactoryMethod(XMethodElement method) { 324 validateNotGeneric(method); 325 326 if (!validateFactoryMethodReturnType(method)) { 327 // If we can't determine that the single method is a valid factory method, don't bother 328 // validating its parameters. 329 return; 330 } 331 332 for (XExecutableParameterElement parameter : method.getParameters()) { 333 if (!parameter.hasAnnotation(TypeNames.BINDS_INSTANCE) 334 && isPrimitive(parameter.getType())) { 335 error( 336 method, 337 messages.nonBindsInstanceParametersMayNotBePrimitives(), 338 messages.inheritedNonBindsInstanceParametersMayNotBePrimitives()); 339 } 340 } 341 } 342 343 /** 344 * Validates that the factory method that actually returns a new component instance. Returns 345 * true if the return type was valid. 346 */ validateFactoryMethodReturnType(XMethodElement method)347 private boolean validateFactoryMethodReturnType(XMethodElement method) { 348 XTypeElement component = creator.getEnclosingTypeElement(); 349 XType returnType = method.asMemberOf(creator.getType()).getReturnType(); 350 if (!isSubtype(component.getType(), returnType)) { 351 error( 352 method, 353 messages.factoryMethodMustReturnComponentType(), 354 messages.inheritedFactoryMethodMustReturnComponentType()); 355 return false; 356 } 357 358 if (method.hasAnnotation(TypeNames.BINDS_INSTANCE)) { 359 error( 360 method, 361 messages.factoryMethodMayNotBeAnnotatedWithBindsInstance(), 362 messages.inheritedFactoryMethodMayNotBeAnnotatedWithBindsInstance()); 363 return false; 364 } 365 366 if (!returnType.isSameType(component.getType())) { 367 // TODO(ronshapiro): Ideally this shouldn't return methods which are redeclared from a 368 // supertype, but do not change the return type. We don't have a good/simple way of checking 369 // that, and it doesn't seem likely, so the warning won't be too bad. 370 ImmutableSet<XMethodElement> declaredMethods = 371 ImmutableSet.copyOf(component.getDeclaredMethods()); 372 if (!declaredMethods.isEmpty()) { 373 report.addWarning( 374 messages.factoryMethodReturnsSupertypeWithMissingMethods( 375 component, creator, returnType, method, declaredMethods), 376 method); 377 } 378 } 379 return true; 380 } 381 382 /** 383 * Generates one of two error messages. If the method is enclosed in the subject, we target the 384 * error to the method itself. Otherwise we target the error to the subject and list the method 385 * as an argument. (Otherwise we have no way of knowing if the method is being compiled in this 386 * pass too, so javac might not be able to pinpoint it's line of code.) 387 */ 388 /* 389 * For Component.Builder, the prototypical example would be if someone had: 390 * libfoo: interface SharedBuilder { void badSetter(A a, B b); } 391 * libbar: BarComponent { BarBuilder extends SharedBuilder } } 392 * ... the compiler only validates BarBuilder when compiling libbar, but it fails because 393 * of libfoo's SharedBuilder (which could have been compiled in a previous pass). 394 * So we can't point to SharedBuilder#badSetter as the subject of the BarBuilder validation 395 * failure. 396 * 397 * This check is a little more strict than necessary -- ideally we'd check if method's enclosing 398 * class was included in this compile run. But that's hard, and this is close enough. 399 */ error( XMethodElement method, String enclosedError, String inheritedError, Object... extraArgs)400 private void error( 401 XMethodElement method, String enclosedError, String inheritedError, Object... extraArgs) { 402 if (method.getEnclosingElement().equals(creator)) { 403 report.addError(String.format(enclosedError, extraArgs), method); 404 } else { 405 report.addError( 406 String.format( 407 inheritedError, 408 ObjectArrays.concat(extraArgs, methodSignatureFormatter.format(method)))); 409 } 410 } 411 412 /** Validates that the given {@code method} is not generic. * */ validateNotGeneric(XMethodElement method)413 private void validateNotGeneric(XMethodElement method) { 414 if (hasTypeParameters(method)) { 415 error( 416 method, 417 messages.methodsMayNotHaveTypeParameters(), 418 messages.inheritedMethodsMayNotHaveTypeParameters()); 419 } 420 } 421 } 422 } 423