• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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