• 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.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