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.auto.common.MoreElements.asType; 20 import static com.google.auto.common.MoreElements.isAnnotationPresent; 21 import static dagger.internal.codegen.base.Scopes.scopesOf; 22 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; 23 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; 24 import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; 25 import static javax.lang.model.element.Modifier.ABSTRACT; 26 import static javax.lang.model.element.Modifier.FINAL; 27 import static javax.lang.model.element.Modifier.PRIVATE; 28 import static javax.lang.model.element.Modifier.STATIC; 29 import static javax.lang.model.type.TypeKind.DECLARED; 30 31 import com.google.auto.common.MoreElements; 32 import com.google.auto.common.MoreTypes; 33 import com.google.common.collect.ImmutableSet; 34 import dagger.assisted.AssistedInject; 35 import dagger.internal.codegen.base.ClearableCache; 36 import dagger.internal.codegen.binding.InjectionAnnotations; 37 import dagger.internal.codegen.compileroption.CompilerOptions; 38 import dagger.internal.codegen.kotlin.KotlinMetadataUtil; 39 import dagger.internal.codegen.langmodel.Accessibility; 40 import dagger.internal.codegen.langmodel.DaggerElements; 41 import dagger.internal.codegen.langmodel.DaggerTypes; 42 import dagger.model.Scope; 43 import java.util.HashMap; 44 import java.util.Map; 45 import java.util.Optional; 46 import java.util.Set; 47 import javax.inject.Inject; 48 import javax.inject.Singleton; 49 import javax.lang.model.element.AnnotationMirror; 50 import javax.lang.model.element.Element; 51 import javax.lang.model.element.ExecutableElement; 52 import javax.lang.model.element.Modifier; 53 import javax.lang.model.element.TypeElement; 54 import javax.lang.model.element.VariableElement; 55 import javax.lang.model.type.TypeKind; 56 import javax.lang.model.type.TypeMirror; 57 import javax.lang.model.util.ElementFilter; 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 private final DaggerTypes types; 68 private final DaggerElements elements; 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 KotlinMetadataUtil metadataUtil; 74 private final Map<ExecutableElement, ValidationReport<TypeElement>> reports = new HashMap<>(); 75 76 @Inject InjectValidator( DaggerTypes types, DaggerElements elements, DependencyRequestValidator dependencyRequestValidator, CompilerOptions compilerOptions, InjectionAnnotations injectionAnnotations, KotlinMetadataUtil metadataUtil)77 InjectValidator( 78 DaggerTypes types, 79 DaggerElements elements, 80 DependencyRequestValidator dependencyRequestValidator, 81 CompilerOptions compilerOptions, 82 InjectionAnnotations injectionAnnotations, 83 KotlinMetadataUtil metadataUtil) { 84 this( 85 types, 86 elements, 87 compilerOptions, 88 dependencyRequestValidator, 89 Optional.empty(), 90 injectionAnnotations, 91 metadataUtil); 92 } 93 InjectValidator( DaggerTypes types, DaggerElements elements, CompilerOptions compilerOptions, DependencyRequestValidator dependencyRequestValidator, Optional<Kind> privateAndStaticInjectionDiagnosticKind, InjectionAnnotations injectionAnnotations, KotlinMetadataUtil metadataUtil)94 private InjectValidator( 95 DaggerTypes types, 96 DaggerElements elements, 97 CompilerOptions compilerOptions, 98 DependencyRequestValidator dependencyRequestValidator, 99 Optional<Kind> privateAndStaticInjectionDiagnosticKind, 100 InjectionAnnotations injectionAnnotations, 101 KotlinMetadataUtil metadataUtil) { 102 this.types = types; 103 this.elements = elements; 104 this.compilerOptions = compilerOptions; 105 this.dependencyRequestValidator = dependencyRequestValidator; 106 this.privateAndStaticInjectionDiagnosticKind = privateAndStaticInjectionDiagnosticKind; 107 this.injectionAnnotations = injectionAnnotations; 108 this.metadataUtil = metadataUtil; 109 } 110 111 @Override clearCache()112 public void clearCache() { 113 reports.clear(); 114 } 115 116 /** 117 * Returns a new validator that performs the same validation as this one, but is strict about 118 * rejecting optionally-specified JSR 330 behavior that Dagger doesn't support (unless {@code 119 * -Adagger.ignorePrivateAndStaticInjectionForComponent=enabled} was set in the javac options). 120 */ whenGeneratingCode()121 public InjectValidator whenGeneratingCode() { 122 return compilerOptions.ignorePrivateAndStaticInjectionForComponent() 123 ? this 124 : new InjectValidator( 125 types, 126 elements, 127 compilerOptions, 128 dependencyRequestValidator, 129 Optional.of(Diagnostic.Kind.ERROR), 130 injectionAnnotations, 131 metadataUtil); 132 } 133 validateConstructor(ExecutableElement constructorElement)134 public ValidationReport<TypeElement> validateConstructor(ExecutableElement constructorElement) { 135 return reentrantComputeIfAbsent(reports, constructorElement, this::validateConstructorUncached); 136 } 137 validateConstructorUncached( ExecutableElement constructorElement)138 private ValidationReport<TypeElement> validateConstructorUncached( 139 ExecutableElement constructorElement) { 140 ValidationReport.Builder<TypeElement> builder = 141 ValidationReport.about(asType(constructorElement.getEnclosingElement())); 142 143 if (isAnnotationPresent(constructorElement, Inject.class) 144 && isAnnotationPresent(constructorElement, AssistedInject.class)) { 145 builder.addError("Constructors cannot be annotated with both @Inject and @AssistedInject"); 146 } 147 148 Class<?> injectAnnotation = 149 isAnnotationPresent(constructorElement, Inject.class) ? Inject.class : AssistedInject.class; 150 151 if (constructorElement.getModifiers().contains(PRIVATE)) { 152 builder.addError( 153 "Dagger does not support injection into private constructors", constructorElement); 154 } 155 156 for (AnnotationMirror qualifier : injectionAnnotations.getQualifiers(constructorElement)) { 157 builder.addError( 158 String.format( 159 "@Qualifier annotations are not allowed on @%s constructors", 160 injectAnnotation.getSimpleName()), 161 constructorElement, 162 qualifier); 163 } 164 165 String scopeErrorMsg = 166 String.format( 167 "@Scope annotations are not allowed on @%s constructors", 168 injectAnnotation.getSimpleName()); 169 170 if (injectAnnotation == Inject.class) { 171 scopeErrorMsg += "; annotate the class instead"; 172 } 173 174 for (Scope scope : scopesOf(constructorElement)) { 175 builder.addError(scopeErrorMsg, constructorElement, scope.scopeAnnotation()); 176 } 177 178 for (VariableElement parameter : constructorElement.getParameters()) { 179 validateDependencyRequest(builder, parameter); 180 } 181 182 if (throwsCheckedExceptions(constructorElement)) { 183 builder.addItem( 184 String.format( 185 "Dagger does not support checked exceptions on @%s constructors", 186 injectAnnotation.getSimpleName()), 187 privateMemberDiagnosticKind(), 188 constructorElement); 189 } 190 191 checkInjectIntoPrivateClass(constructorElement, builder); 192 193 TypeElement enclosingElement = 194 MoreElements.asType(constructorElement.getEnclosingElement()); 195 196 Set<Modifier> typeModifiers = enclosingElement.getModifiers(); 197 if (typeModifiers.contains(ABSTRACT)) { 198 builder.addError( 199 String.format( 200 "@%s is nonsense on the constructor of an abstract class", 201 injectAnnotation.getSimpleName()), 202 constructorElement); 203 } 204 205 if (enclosingElement.getNestingKind().isNested() 206 && !typeModifiers.contains(STATIC)) { 207 builder.addError( 208 String.format( 209 "@%s constructors are invalid on inner classes. " 210 + "Did you mean to make the class static?", 211 injectAnnotation.getSimpleName()), 212 constructorElement); 213 } 214 215 // This is computationally expensive, but probably preferable to a giant index 216 ImmutableSet<ExecutableElement> injectConstructors = 217 ImmutableSet.<ExecutableElement>builder() 218 .addAll(injectedConstructors(enclosingElement)) 219 .addAll(assistedInjectedConstructors(enclosingElement)) 220 .build(); 221 222 if (injectConstructors.size() > 1) { 223 builder.addError("Types may only contain one injected constructor", constructorElement); 224 } 225 226 ImmutableSet<Scope> scopes = scopesOf(enclosingElement); 227 if (injectAnnotation == AssistedInject.class) { 228 for (Scope scope : scopes) { 229 builder.addError( 230 "A type with an @AssistedInject-annotated constructor cannot be scoped", 231 enclosingElement, 232 scope.scopeAnnotation()); 233 } 234 } else if (scopes.size() > 1) { 235 for (Scope scope : scopes) { 236 builder.addError( 237 "A single binding may not declare more than one @Scope", 238 enclosingElement, 239 scope.scopeAnnotation()); 240 } 241 } 242 243 return builder.build(); 244 } 245 validateField(VariableElement fieldElement)246 private ValidationReport<VariableElement> validateField(VariableElement fieldElement) { 247 ValidationReport.Builder<VariableElement> builder = ValidationReport.about(fieldElement); 248 Set<Modifier> modifiers = fieldElement.getModifiers(); 249 if (modifiers.contains(FINAL)) { 250 builder.addError("@Inject fields may not be final", fieldElement); 251 } 252 253 if (modifiers.contains(PRIVATE)) { 254 builder.addItem( 255 "Dagger does not support injection into private fields", 256 privateMemberDiagnosticKind(), 257 fieldElement); 258 } 259 260 if (modifiers.contains(STATIC)) { 261 builder.addItem( 262 "Dagger does not support injection into static fields", 263 staticMemberDiagnosticKind(), 264 fieldElement); 265 } 266 267 validateDependencyRequest(builder, fieldElement); 268 269 return builder.build(); 270 } 271 validateMethod(ExecutableElement methodElement)272 private ValidationReport<ExecutableElement> validateMethod(ExecutableElement methodElement) { 273 ValidationReport.Builder<ExecutableElement> builder = ValidationReport.about(methodElement); 274 Set<Modifier> modifiers = methodElement.getModifiers(); 275 if (modifiers.contains(ABSTRACT)) { 276 builder.addError("Methods with @Inject may not be abstract", methodElement); 277 } 278 279 if (modifiers.contains(PRIVATE)) { 280 builder.addItem( 281 "Dagger does not support injection into private methods", 282 privateMemberDiagnosticKind(), 283 methodElement); 284 } 285 286 if (modifiers.contains(STATIC)) { 287 builder.addItem( 288 "Dagger does not support injection into static methods", 289 staticMemberDiagnosticKind(), 290 methodElement); 291 } 292 293 if (!methodElement.getTypeParameters().isEmpty()) { 294 builder.addError("Methods with @Inject may not declare type parameters", methodElement); 295 } 296 297 if (!methodElement.getThrownTypes().isEmpty()) { 298 builder.addError("Methods with @Inject may not throw checked exceptions. " 299 + "Please wrap your exceptions in a RuntimeException instead.", methodElement); 300 } 301 302 for (VariableElement parameter : methodElement.getParameters()) { 303 validateDependencyRequest(builder, parameter); 304 } 305 306 return builder.build(); 307 } 308 validateDependencyRequest( ValidationReport.Builder<?> builder, VariableElement parameter)309 private void validateDependencyRequest( 310 ValidationReport.Builder<?> builder, VariableElement parameter) { 311 dependencyRequestValidator.validateDependencyRequest(builder, parameter, parameter.asType()); 312 dependencyRequestValidator.checkNotProducer(builder, parameter); 313 } 314 validateMembersInjectionType(TypeElement typeElement)315 public ValidationReport<TypeElement> validateMembersInjectionType(TypeElement typeElement) { 316 // TODO(beder): This element might not be currently compiled, so this error message could be 317 // left in limbo. Find an appropriate way to display the error message in that case. 318 ValidationReport.Builder<TypeElement> builder = ValidationReport.about(typeElement); 319 boolean hasInjectedMembers = false; 320 for (VariableElement element : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) { 321 if (MoreElements.isAnnotationPresent(element, Inject.class)) { 322 hasInjectedMembers = true; 323 ValidationReport<VariableElement> report = validateField(element); 324 if (!report.isClean()) { 325 builder.addSubreport(report); 326 } 327 } 328 } 329 for (ExecutableElement element : ElementFilter.methodsIn(typeElement.getEnclosedElements())) { 330 if (MoreElements.isAnnotationPresent(element, Inject.class)) { 331 hasInjectedMembers = true; 332 ValidationReport<ExecutableElement> report = validateMethod(element); 333 if (!report.isClean()) { 334 builder.addSubreport(report); 335 } 336 } 337 } 338 339 if (hasInjectedMembers) { 340 checkInjectIntoPrivateClass(typeElement, builder); 341 checkInjectIntoKotlinObject(typeElement, builder); 342 } 343 TypeMirror superclass = typeElement.getSuperclass(); 344 if (!superclass.getKind().equals(TypeKind.NONE)) { 345 ValidationReport<TypeElement> report = validateType(MoreTypes.asTypeElement(superclass)); 346 if (!report.isClean()) { 347 builder.addSubreport(report); 348 } 349 } 350 return builder.build(); 351 } 352 validateType(TypeElement typeElement)353 public ValidationReport<TypeElement> validateType(TypeElement typeElement) { 354 ValidationReport.Builder<TypeElement> builder = ValidationReport.about(typeElement); 355 ValidationReport<TypeElement> membersInjectionReport = 356 validateMembersInjectionType(typeElement); 357 if (!membersInjectionReport.isClean()) { 358 builder.addSubreport(membersInjectionReport); 359 } 360 for (ExecutableElement element : 361 ElementFilter.constructorsIn(typeElement.getEnclosedElements())) { 362 if (isAnnotationPresent(element, Inject.class) 363 || isAnnotationPresent(element, AssistedInject.class)) { 364 ValidationReport<TypeElement> report = validateConstructor(element); 365 if (!report.isClean()) { 366 builder.addSubreport(report); 367 } 368 } 369 } 370 return builder.build(); 371 } 372 isValidType(TypeMirror type)373 public boolean isValidType(TypeMirror type) { 374 if (!type.getKind().equals(DECLARED)) { 375 return true; 376 } 377 return validateType(MoreTypes.asTypeElement(type)).isClean(); 378 } 379 380 /** Returns true if the given method element declares a checked exception. */ throwsCheckedExceptions(ExecutableElement methodElement)381 private boolean throwsCheckedExceptions(ExecutableElement methodElement) { 382 TypeMirror runtimeExceptionType = elements.getTypeElement(RuntimeException.class).asType(); 383 TypeMirror errorType = elements.getTypeElement(Error.class).asType(); 384 for (TypeMirror thrownType : methodElement.getThrownTypes()) { 385 if (!types.isSubtype(thrownType, runtimeExceptionType) 386 && !types.isSubtype(thrownType, errorType)) { 387 return true; 388 } 389 } 390 return false; 391 } 392 checkInjectIntoPrivateClass( Element element, ValidationReport.Builder<TypeElement> builder)393 private void checkInjectIntoPrivateClass( 394 Element element, ValidationReport.Builder<TypeElement> builder) { 395 if (!Accessibility.isElementAccessibleFromOwnPackage( 396 DaggerElements.closestEnclosingTypeElement(element))) { 397 builder.addItem( 398 "Dagger does not support injection into private classes", 399 privateMemberDiagnosticKind(), 400 element); 401 } 402 } 403 checkInjectIntoKotlinObject( TypeElement element, ValidationReport.Builder<TypeElement> builder)404 private void checkInjectIntoKotlinObject( 405 TypeElement element, ValidationReport.Builder<TypeElement> builder) { 406 if (metadataUtil.isObjectClass(element) || metadataUtil.isCompanionObjectClass(element)) { 407 builder.addError("Dagger does not support injection into Kotlin objects", element); 408 } 409 } 410 privateMemberDiagnosticKind()411 private Diagnostic.Kind privateMemberDiagnosticKind() { 412 return privateAndStaticInjectionDiagnosticKind.orElse( 413 compilerOptions.privateMemberValidationKind()); 414 } 415 staticMemberDiagnosticKind()416 private Diagnostic.Kind staticMemberDiagnosticKind() { 417 return privateAndStaticInjectionDiagnosticKind.orElse( 418 compilerOptions.staticMemberValidationKind()); 419 } 420 } 421