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.Util.reentrantComputeIfAbsent; 21 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; 22 import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; 23 import static dagger.internal.codegen.binding.SourceFiles.factoryNameForElement; 24 import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; 25 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 26 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; 27 import static dagger.internal.codegen.xprocessing.XElements.getAnyAnnotation; 28 import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters; 29 import static dagger.internal.codegen.xprocessing.XTypes.isSubtype; 30 31 import androidx.room.compiler.processing.XAnnotation; 32 import androidx.room.compiler.processing.XConstructorElement; 33 import androidx.room.compiler.processing.XElement; 34 import androidx.room.compiler.processing.XExecutableParameterElement; 35 import androidx.room.compiler.processing.XFieldElement; 36 import androidx.room.compiler.processing.XMethodElement; 37 import androidx.room.compiler.processing.XProcessingEnv; 38 import androidx.room.compiler.processing.XType; 39 import androidx.room.compiler.processing.XTypeElement; 40 import androidx.room.compiler.processing.XVariableElement; 41 import com.google.common.collect.ImmutableSet; 42 import com.squareup.javapoet.ClassName; 43 import com.squareup.javapoet.TypeName; 44 import dagger.internal.codegen.base.ClearableCache; 45 import dagger.internal.codegen.base.DaggerSuperficialValidation; 46 import dagger.internal.codegen.binding.InjectionAnnotations; 47 import dagger.internal.codegen.binding.MethodSignatureFormatter; 48 import dagger.internal.codegen.compileroption.CompilerOptions; 49 import dagger.internal.codegen.javapoet.TypeNames; 50 import dagger.internal.codegen.langmodel.Accessibility; 51 import dagger.internal.codegen.model.Scope; 52 import dagger.internal.codegen.xprocessing.XAnnotations; 53 import java.util.HashMap; 54 import java.util.Map; 55 import java.util.Optional; 56 import javax.inject.Inject; 57 import javax.inject.Singleton; 58 import javax.tools.Diagnostic; 59 import javax.tools.Diagnostic.Kind; 60 61 /** 62 * A {@linkplain ValidationReport validator} for {@link Inject}-annotated elements and the types 63 * that contain them. 64 */ 65 @Singleton 66 public final class InjectValidator implements ClearableCache { 67 68 private final XProcessingEnv processingEnv; 69 private final CompilerOptions compilerOptions; 70 private final DependencyRequestValidator dependencyRequestValidator; 71 private final Optional<Diagnostic.Kind> privateAndStaticInjectionDiagnosticKind; 72 private final InjectionAnnotations injectionAnnotations; 73 private final DaggerSuperficialValidation superficialValidation; 74 private final Map<XTypeElement, ValidationReport> provisionReports = new HashMap<>(); 75 private final Map<XTypeElement, ValidationReport> membersInjectionReports = new HashMap<>(); 76 private final MethodSignatureFormatter methodSignatureFormatter; 77 78 @Inject InjectValidator( XProcessingEnv processingEnv, DependencyRequestValidator dependencyRequestValidator, CompilerOptions compilerOptions, InjectionAnnotations injectionAnnotations, DaggerSuperficialValidation superficialValidation, MethodSignatureFormatter methodSignatureFormatter)79 InjectValidator( 80 XProcessingEnv processingEnv, 81 DependencyRequestValidator dependencyRequestValidator, 82 CompilerOptions compilerOptions, 83 InjectionAnnotations injectionAnnotations, 84 DaggerSuperficialValidation superficialValidation, 85 MethodSignatureFormatter methodSignatureFormatter) { 86 this( 87 processingEnv, 88 compilerOptions, 89 dependencyRequestValidator, 90 Optional.empty(), 91 injectionAnnotations, 92 superficialValidation, 93 methodSignatureFormatter); 94 } 95 InjectValidator( XProcessingEnv processingEnv, CompilerOptions compilerOptions, DependencyRequestValidator dependencyRequestValidator, Optional<Kind> privateAndStaticInjectionDiagnosticKind, InjectionAnnotations injectionAnnotations, DaggerSuperficialValidation superficialValidation, MethodSignatureFormatter methodSignatureFormatter)96 private InjectValidator( 97 XProcessingEnv processingEnv, 98 CompilerOptions compilerOptions, 99 DependencyRequestValidator dependencyRequestValidator, 100 Optional<Kind> privateAndStaticInjectionDiagnosticKind, 101 InjectionAnnotations injectionAnnotations, 102 DaggerSuperficialValidation superficialValidation, 103 MethodSignatureFormatter methodSignatureFormatter) { 104 this.processingEnv = processingEnv; 105 this.compilerOptions = compilerOptions; 106 this.dependencyRequestValidator = dependencyRequestValidator; 107 this.privateAndStaticInjectionDiagnosticKind = privateAndStaticInjectionDiagnosticKind; 108 this.injectionAnnotations = injectionAnnotations; 109 this.superficialValidation = superficialValidation; 110 this.methodSignatureFormatter = methodSignatureFormatter; 111 } 112 113 @Override clearCache()114 public void clearCache() { 115 provisionReports.clear(); 116 membersInjectionReports.clear(); 117 } 118 119 /** 120 * Returns a new validator that performs the same validation as this one, but is strict about 121 * rejecting optionally-specified JSR 330 behavior that Dagger doesn't support (unless {@code 122 * -Adagger.ignorePrivateAndStaticInjectionForComponent=enabled} was set in the javac options). 123 */ whenGeneratingCode()124 public InjectValidator whenGeneratingCode() { 125 return compilerOptions.ignorePrivateAndStaticInjectionForComponent() 126 ? this 127 : new InjectValidator( 128 processingEnv, 129 compilerOptions, 130 dependencyRequestValidator, 131 Optional.of(Diagnostic.Kind.ERROR), 132 injectionAnnotations, 133 superficialValidation, 134 methodSignatureFormatter); 135 } 136 validate(XTypeElement typeElement)137 public ValidationReport validate(XTypeElement typeElement) { 138 return reentrantComputeIfAbsent(provisionReports, typeElement, this::validateUncached); 139 } 140 validateUncached(XTypeElement typeElement)141 private ValidationReport validateUncached(XTypeElement typeElement) { 142 ValidationReport.Builder builder = ValidationReport.about(typeElement); 143 builder.addSubreport(validateForMembersInjectionInternal(typeElement)); 144 145 ImmutableSet<XConstructorElement> injectConstructors = 146 ImmutableSet.<XConstructorElement>builder() 147 .addAll(injectedConstructors(typeElement)) 148 .addAll(assistedInjectedConstructors(typeElement)) 149 .build(); 150 151 switch (injectConstructors.size()) { 152 case 0: 153 break; // Nothing to validate. 154 case 1: 155 builder.addSubreport(validateConstructor(getOnlyElement(injectConstructors))); 156 break; 157 default: 158 builder.addError( 159 String.format( 160 "Type %s may only contain one injected constructor. Found: %s", 161 typeElement.getQualifiedName(), 162 injectConstructors.stream() 163 .map(methodSignatureFormatter::format) 164 .collect(toImmutableList())), 165 typeElement); 166 } 167 168 return builder.build(); 169 } 170 validateConstructor(XConstructorElement constructorElement)171 private ValidationReport validateConstructor(XConstructorElement constructorElement) { 172 superficialValidation.validateTypeOf(constructorElement); 173 ValidationReport.Builder builder = 174 ValidationReport.about(constructorElement.getEnclosingElement()); 175 176 if (InjectionAnnotations.hasInjectAnnotation(constructorElement) 177 && constructorElement.hasAnnotation(TypeNames.ASSISTED_INJECT)) { 178 builder.addError("Constructors cannot be annotated with both @Inject and @AssistedInject"); 179 } 180 181 ClassName injectAnnotation = 182 getAnyAnnotation( 183 constructorElement, 184 TypeNames.INJECT, 185 TypeNames.INJECT_JAVAX, 186 TypeNames.ASSISTED_INJECT) 187 .map(XAnnotations::getClassName) 188 .get(); 189 190 if (constructorElement.isPrivate()) { 191 builder.addError( 192 "Dagger does not support injection into private constructors", constructorElement); 193 } 194 195 // If this type has already been processed in a previous round or compilation unit then there 196 // is no reason to recheck for invalid scope annotations since it's already been checked. 197 // This allows us to skip superficial validation of constructor annotations in subsequent 198 // compilations where the annotation types may no longer be on the classpath. 199 if (!processedInPreviousRoundOrCompilationUnit(constructorElement)) { 200 superficialValidation.validateAnnotationsOf(constructorElement); 201 for (XAnnotation qualifier : injectionAnnotations.getQualifiers(constructorElement)) { 202 builder.addError( 203 String.format( 204 "@Qualifier annotations are not allowed on @%s constructors", 205 injectAnnotation.simpleName()), 206 constructorElement, 207 qualifier); 208 } 209 210 String scopeErrorMsg = 211 String.format( 212 "@Scope annotations are not allowed on @%s constructors", 213 injectAnnotation.simpleName()); 214 215 if (injectAnnotation.equals(TypeNames.INJECT) 216 || injectAnnotation.equals(TypeNames.INJECT_JAVAX)) { 217 scopeErrorMsg += "; annotate the class instead"; 218 } 219 220 for (Scope scope : injectionAnnotations.getScopes(constructorElement)) { 221 builder.addError(scopeErrorMsg, constructorElement, scope.scopeAnnotation().xprocessing()); 222 } 223 } 224 225 for (XExecutableParameterElement parameter : constructorElement.getParameters()) { 226 superficialValidation.validateTypeOf(parameter); 227 validateDependencyRequest(builder, parameter); 228 } 229 230 if (throwsCheckedExceptions(constructorElement)) { 231 builder.addItem( 232 String.format( 233 "Dagger does not support checked exceptions on @%s constructors", 234 injectAnnotation.simpleName()), 235 privateMemberDiagnosticKind(), 236 constructorElement); 237 } 238 239 checkInjectIntoPrivateClass(constructorElement, builder); 240 241 XTypeElement enclosingElement = constructorElement.getEnclosingElement(); 242 if (enclosingElement.isAbstract()) { 243 builder.addError( 244 String.format( 245 "@%s is nonsense on the constructor of an abstract class", 246 injectAnnotation.simpleName()), 247 constructorElement); 248 } 249 250 if (enclosingElement.isNested() && !enclosingElement.isStatic()) { 251 builder.addError( 252 String.format( 253 "@%s constructors are invalid on inner classes. " 254 + "Did you mean to make the class static?", 255 injectAnnotation.simpleName()), 256 constructorElement); 257 } 258 259 // Note: superficial validation of the annotations is done as part of getting the scopes. 260 ImmutableSet<Scope> scopes = 261 injectionAnnotations.getScopes(constructorElement.getEnclosingElement()); 262 if (injectAnnotation.equals(TypeNames.ASSISTED_INJECT)) { 263 for (Scope scope : scopes) { 264 builder.addError( 265 "A type with an @AssistedInject-annotated constructor cannot be scoped", 266 enclosingElement, 267 scope.scopeAnnotation().xprocessing()); 268 } 269 } else if (scopes.size() > 1) { 270 for (Scope scope : scopes) { 271 builder.addError( 272 "A single binding may not declare more than one @Scope", 273 enclosingElement, 274 scope.scopeAnnotation().xprocessing()); 275 } 276 } 277 278 return builder.build(); 279 } 280 validateField(XFieldElement fieldElement)281 private ValidationReport validateField(XFieldElement fieldElement) { 282 superficialValidation.validateTypeOf(fieldElement); 283 ValidationReport.Builder builder = ValidationReport.about(fieldElement); 284 if (fieldElement.isFinal()) { 285 builder.addError("@Inject fields may not be final", fieldElement); 286 } 287 288 if (fieldElement.isPrivate()) { 289 builder.addItem( 290 "Dagger does not support injection into private fields", 291 privateMemberDiagnosticKind(), 292 fieldElement); 293 } 294 295 if (fieldElement.isStatic()) { 296 builder.addItem( 297 "Dagger does not support injection into static fields", 298 staticMemberDiagnosticKind(), 299 fieldElement); 300 } 301 302 if (fieldElement.isProtected() 303 && fieldElement.getEnclosingElement().isFromKotlin() 304 ) { 305 builder.addItem( 306 "Dagger injector does not have access to kotlin protected fields", 307 staticMemberDiagnosticKind(), 308 fieldElement); 309 } 310 311 validateDependencyRequest(builder, fieldElement); 312 313 return builder.build(); 314 } 315 validateMethod(XMethodElement methodElement)316 private ValidationReport validateMethod(XMethodElement methodElement) { 317 superficialValidation.validateTypeOf(methodElement); 318 ValidationReport.Builder builder = ValidationReport.about(methodElement); 319 if (methodElement.isAbstract()) { 320 builder.addError("Methods with @Inject may not be abstract", methodElement); 321 } 322 323 if (methodElement.isPrivate()) { 324 builder.addItem( 325 "Dagger does not support injection into private methods", 326 privateMemberDiagnosticKind(), 327 methodElement); 328 } 329 330 if (methodElement.isStatic()) { 331 builder.addItem( 332 "Dagger does not support injection into static methods", 333 staticMemberDiagnosticKind(), 334 methodElement); 335 } 336 337 // No need to resolve type parameters since we're only checking existence. 338 if (hasTypeParameters(methodElement)) { 339 builder.addError("Methods with @Inject may not declare type parameters", methodElement); 340 } 341 342 // No need to resolve thrown types since we're only checking existence. 343 if (!methodElement.getThrownTypes().isEmpty()) { 344 builder.addError( 345 "Methods with @Inject may not throw checked exceptions. " 346 + "Please wrap your exceptions in a RuntimeException instead.", 347 methodElement); 348 } 349 350 for (XExecutableParameterElement parameter : methodElement.getParameters()) { 351 superficialValidation.validateTypeOf(parameter); 352 validateDependencyRequest(builder, parameter); 353 } 354 355 return builder.build(); 356 } 357 validateDependencyRequest( ValidationReport.Builder builder, XVariableElement parameter)358 private void validateDependencyRequest( 359 ValidationReport.Builder builder, XVariableElement parameter) { 360 dependencyRequestValidator.validateDependencyRequest(builder, parameter, parameter.getType()); 361 dependencyRequestValidator.checkNotProducer(builder, parameter); 362 } 363 validateForMembersInjection(XTypeElement typeElement)364 public ValidationReport validateForMembersInjection(XTypeElement typeElement) { 365 return !processedInPreviousRoundOrCompilationUnit(typeElement) 366 ? validate(typeElement) // validate everything 367 : validateForMembersInjectionInternal(typeElement); // validate only inject members 368 } 369 validateForMembersInjectionInternal(XTypeElement typeElement)370 private ValidationReport validateForMembersInjectionInternal(XTypeElement typeElement) { 371 return reentrantComputeIfAbsent( 372 membersInjectionReports, typeElement, this::validateForMembersInjectionInternalUncached); 373 } 374 validateForMembersInjectionInternalUncached(XTypeElement typeElement)375 private ValidationReport validateForMembersInjectionInternalUncached(XTypeElement typeElement) { 376 superficialValidation.validateTypeOf(typeElement); 377 // TODO(beder): This element might not be currently compiled, so this error message could be 378 // left in limbo. Find an appropriate way to display the error message in that case. 379 ValidationReport.Builder builder = ValidationReport.about(typeElement); 380 boolean hasInjectedMembers = false; 381 for (XFieldElement field : typeElement.getDeclaredFields()) { 382 if (InjectionAnnotations.hasInjectAnnotation(field)) { 383 hasInjectedMembers = true; 384 ValidationReport report = validateField(field); 385 if (!report.isClean()) { 386 builder.addSubreport(report); 387 } 388 } 389 } 390 for (XMethodElement method : typeElement.getDeclaredMethods()) { 391 if (InjectionAnnotations.hasInjectAnnotation(method)) { 392 hasInjectedMembers = true; 393 ValidationReport report = validateMethod(method); 394 if (!report.isClean()) { 395 builder.addSubreport(report); 396 } 397 } 398 } 399 400 if (hasInjectedMembers) { 401 checkInjectIntoPrivateClass(typeElement, builder); 402 checkInjectIntoKotlinObject(typeElement, builder); 403 } 404 405 Optional.ofNullable(typeElement.getSuperType()) 406 .filter(supertype -> !supertype.getTypeName().equals(TypeName.OBJECT)) 407 .ifPresent( 408 supertype -> { 409 superficialValidation.validateSuperTypeOf(typeElement); 410 ValidationReport report = validateForMembersInjection(supertype.getTypeElement()); 411 if (!report.isClean()) { 412 builder.addSubreport(report); 413 } 414 }); 415 416 return builder.build(); 417 } 418 419 /** Returns true if the given method element declares a checked exception. */ throwsCheckedExceptions(XConstructorElement constructorElement)420 private boolean throwsCheckedExceptions(XConstructorElement constructorElement) { 421 XType runtimeException = processingEnv.findType(TypeNames.RUNTIME_EXCEPTION); 422 XType error = processingEnv.findType(TypeNames.ERROR); 423 superficialValidation.validateThrownTypesOf(constructorElement); 424 return !constructorElement.getThrownTypes().stream() 425 .allMatch(type -> isSubtype(type, runtimeException) || isSubtype(type, error)); 426 } 427 checkInjectIntoPrivateClass(XElement element, ValidationReport.Builder builder)428 private void checkInjectIntoPrivateClass(XElement element, ValidationReport.Builder builder) { 429 if (!Accessibility.isElementAccessibleFromOwnPackage(closestEnclosingTypeElement(element))) { 430 builder.addItem( 431 "Dagger does not support injection into private classes", 432 privateMemberDiagnosticKind(), 433 element); 434 } 435 } 436 checkInjectIntoKotlinObject(XTypeElement element, ValidationReport.Builder builder)437 private void checkInjectIntoKotlinObject(XTypeElement element, ValidationReport.Builder builder) { 438 if (element.isKotlinObject() || element.isCompanionObject()) { 439 builder.addError("Dagger does not support injection into Kotlin objects", element); 440 } 441 } 442 privateMemberDiagnosticKind()443 private Diagnostic.Kind privateMemberDiagnosticKind() { 444 return privateAndStaticInjectionDiagnosticKind.orElse( 445 compilerOptions.privateMemberValidationKind()); 446 } 447 staticMemberDiagnosticKind()448 private Diagnostic.Kind staticMemberDiagnosticKind() { 449 return privateAndStaticInjectionDiagnosticKind.orElse( 450 compilerOptions.staticMemberValidationKind()); 451 } 452 processedInPreviousRoundOrCompilationUnit(XConstructorElement injectConstructor)453 private boolean processedInPreviousRoundOrCompilationUnit(XConstructorElement injectConstructor) { 454 return processingEnv.findTypeElement(factoryNameForElement(injectConstructor)) != null; 455 } 456 processedInPreviousRoundOrCompilationUnit(XTypeElement membersInjectedType)457 private boolean processedInPreviousRoundOrCompilationUnit(XTypeElement membersInjectedType) { 458 return processingEnv.findTypeElement(membersInjectorNameForType(membersInjectedType)) != null; 459 } 460 } 461