• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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;
18 
19 import static com.google.auto.common.MoreTypes.asDeclared;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.base.Predicates.in;
22 import static com.google.common.collect.Collections2.transform;
23 import static com.google.common.collect.Iterables.getOnlyElement;
24 import static dagger.internal.codegen.ComponentAnnotation.rootComponentAnnotation;
25 import static dagger.internal.codegen.DaggerStreams.toImmutableSet;
26 import static dagger.internal.codegen.DaggerStreams.toImmutableSetMultimap;
27 import static dagger.internal.codegen.DiagnosticFormatting.stripCommonTypePrefixes;
28 import static dagger.internal.codegen.Formatter.INDENT;
29 import static dagger.internal.codegen.Scopes.getReadableSource;
30 import static dagger.internal.codegen.Scopes.scopesOf;
31 import static dagger.internal.codegen.Scopes.singletonScope;
32 import static dagger.internal.codegen.Util.reentrantComputeIfAbsent;
33 import static java.util.stream.Collectors.joining;
34 import static java.util.stream.Collectors.toList;
35 import static javax.tools.Diagnostic.Kind.ERROR;
36 
37 import com.google.auto.common.MoreElements;
38 import com.google.auto.common.MoreTypes;
39 import com.google.common.base.Equivalence.Wrapper;
40 import com.google.common.collect.ImmutableSet;
41 import com.google.common.collect.ImmutableSetMultimap;
42 import com.google.common.collect.Multimaps;
43 import com.google.common.collect.Sets;
44 import dagger.internal.codegen.ComponentRequirement.NullPolicy;
45 import dagger.internal.codegen.ErrorMessages.ComponentCreatorMessages;
46 import dagger.internal.codegen.langmodel.DaggerElements;
47 import dagger.internal.codegen.langmodel.DaggerTypes;
48 import dagger.model.Scope;
49 import java.util.ArrayDeque;
50 import java.util.Collection;
51 import java.util.Deque;
52 import java.util.LinkedHashMap;
53 import java.util.Map;
54 import java.util.Map.Entry;
55 import java.util.Optional;
56 import java.util.Set;
57 import java.util.StringJoiner;
58 import javax.inject.Inject;
59 import javax.lang.model.element.Element;
60 import javax.lang.model.element.ExecutableElement;
61 import javax.lang.model.element.Modifier;
62 import javax.lang.model.element.TypeElement;
63 import javax.lang.model.element.VariableElement;
64 import javax.lang.model.type.DeclaredType;
65 import javax.lang.model.type.ExecutableType;
66 import javax.lang.model.type.TypeMirror;
67 import javax.tools.Diagnostic;
68 
69 /**
70  * Reports errors in the component hierarchy.
71  *
72  * <ul>
73  *   <li>Validates scope hierarchy of component dependencies and subcomponents.
74  *   <li>Reports errors if there are component dependency cycles.
75  *   <li>Reports errors if any abstract modules have non-abstract instance binding methods.
76  *   <li>Validates component creator types.
77  * </ul>
78  */
79 // TODO(dpb): Combine with ComponentHierarchyValidator.
80 final class ComponentDescriptorValidator {
81 
82   private final DaggerElements elements;
83   private final DaggerTypes types;
84   private final CompilerOptions compilerOptions;
85   private final MethodSignatureFormatter methodSignatureFormatter;
86   private final ComponentHierarchyValidator componentHierarchyValidator;
87 
88   @Inject
ComponentDescriptorValidator( DaggerElements elements, DaggerTypes types, CompilerOptions compilerOptions, MethodSignatureFormatter methodSignatureFormatter, ComponentHierarchyValidator componentHierarchyValidator)89   ComponentDescriptorValidator(
90       DaggerElements elements,
91       DaggerTypes types,
92       CompilerOptions compilerOptions,
93       MethodSignatureFormatter methodSignatureFormatter,
94       ComponentHierarchyValidator componentHierarchyValidator) {
95     this.elements = elements;
96     this.types = types;
97     this.compilerOptions = compilerOptions;
98     this.methodSignatureFormatter = methodSignatureFormatter;
99     this.componentHierarchyValidator = componentHierarchyValidator;
100   }
101 
validate(ComponentDescriptor component)102   ValidationReport<TypeElement> validate(ComponentDescriptor component) {
103     ComponentValidation validation = new ComponentValidation(component);
104     validation.visitComponent(component);
105     validation.report(component).addSubreport(componentHierarchyValidator.validate(component));
106     return validation.buildReport();
107   }
108 
109   private final class ComponentValidation {
110     final ComponentDescriptor rootComponent;
111     final Map<ComponentDescriptor, ValidationReport.Builder<TypeElement>> reports =
112         new LinkedHashMap<>();
113 
ComponentValidation(ComponentDescriptor rootComponent)114     ComponentValidation(ComponentDescriptor rootComponent) {
115       this.rootComponent = checkNotNull(rootComponent);
116     }
117 
118     /** Returns a report that contains all validation messages found during traversal. */
buildReport()119     ValidationReport<TypeElement> buildReport() {
120       ValidationReport.Builder<TypeElement> report =
121           ValidationReport.about(rootComponent.typeElement());
122       reports.values().forEach(subreport -> report.addSubreport(subreport.build()));
123       return report.build();
124     }
125 
126     /** Returns the report builder for a (sub)component. */
report(ComponentDescriptor component)127     private ValidationReport.Builder<TypeElement> report(ComponentDescriptor component) {
128       return reentrantComputeIfAbsent(
129           reports, component, descriptor -> ValidationReport.about(descriptor.typeElement()));
130     }
131 
reportComponentItem( Diagnostic.Kind kind, ComponentDescriptor component, String message)132     private void reportComponentItem(
133         Diagnostic.Kind kind, ComponentDescriptor component, String message) {
134       report(component)
135           .addItem(message, kind, component.typeElement(), component.annotation().annotation());
136     }
137 
reportComponentError(ComponentDescriptor component, String error)138     private void reportComponentError(ComponentDescriptor component, String error) {
139       reportComponentItem(ERROR, component, error);
140     }
141 
visitComponent(ComponentDescriptor component)142     void visitComponent(ComponentDescriptor component) {
143       validateDependencyScopes(component);
144       validateComponentDependencyHierarchy(component);
145       validateModules(component);
146       validateCreators(component);
147       component.childComponents().forEach(this::visitComponent);
148     }
149 
150     /** Validates that component dependencies do not form a cycle. */
validateComponentDependencyHierarchy(ComponentDescriptor component)151     private void validateComponentDependencyHierarchy(ComponentDescriptor component) {
152       validateComponentDependencyHierarchy(component, component.typeElement(), new ArrayDeque<>());
153     }
154 
155     /** Recursive method to validate that component dependencies do not form a cycle. */
validateComponentDependencyHierarchy( ComponentDescriptor component, TypeElement dependency, Deque<TypeElement> dependencyStack)156     private void validateComponentDependencyHierarchy(
157         ComponentDescriptor component, TypeElement dependency, Deque<TypeElement> dependencyStack) {
158       if (dependencyStack.contains(dependency)) {
159         // Current component has already appeared in the component chain.
160         StringBuilder message = new StringBuilder();
161         message.append(component.typeElement().getQualifiedName());
162         message.append(" contains a cycle in its component dependencies:\n");
163         dependencyStack.push(dependency);
164         appendIndentedComponentsList(message, dependencyStack);
165         dependencyStack.pop();
166         reportComponentItem(
167             compilerOptions.scopeCycleValidationType().diagnosticKind().get(),
168             component,
169             message.toString());
170       } else {
171         rootComponentAnnotation(dependency)
172             .ifPresent(
173                 componentAnnotation -> {
174                   dependencyStack.push(dependency);
175 
176                   for (TypeElement nextDependency : componentAnnotation.dependencies()) {
177                     validateComponentDependencyHierarchy(
178                         component, nextDependency, dependencyStack);
179                   }
180 
181                   dependencyStack.pop();
182                 });
183       }
184     }
185 
186     /**
187      * Validates that among the dependencies are at most one scoped dependency, that there are no
188      * cycles within the scoping chain, and that singleton components have no scoped dependencies.
189      */
validateDependencyScopes(ComponentDescriptor component)190     private void validateDependencyScopes(ComponentDescriptor component) {
191       ImmutableSet<Scope> scopes = component.scopes();
192       ImmutableSet<TypeElement> scopedDependencies =
193           scopedTypesIn(
194               component
195                   .dependencies()
196                   .stream()
197                   .map(ComponentRequirement::typeElement)
198                   .collect(toImmutableSet()));
199       if (!scopes.isEmpty()) {
200         Scope singletonScope = singletonScope(elements);
201         // Dagger 1.x scope compatibility requires this be suppress-able.
202         if (compilerOptions.scopeCycleValidationType().diagnosticKind().isPresent()
203             && scopes.contains(singletonScope)) {
204           // Singleton is a special-case representing the longest lifetime, and therefore
205           // @Singleton components may not depend on scoped components
206           if (!scopedDependencies.isEmpty()) {
207             StringBuilder message =
208                 new StringBuilder(
209                     "This @Singleton component cannot depend on scoped components:\n");
210             appendIndentedComponentsList(message, scopedDependencies);
211             reportComponentItem(
212                 compilerOptions.scopeCycleValidationType().diagnosticKind().get(),
213                 component,
214                 message.toString());
215           }
216         } else if (scopedDependencies.size() > 1) {
217           // Scoped components may depend on at most one scoped component.
218           StringBuilder message = new StringBuilder();
219           for (Scope scope : scopes) {
220             message.append(getReadableSource(scope)).append(' ');
221           }
222           message
223               .append(component.typeElement().getQualifiedName())
224               .append(" depends on more than one scoped component:\n");
225           appendIndentedComponentsList(message, scopedDependencies);
226           reportComponentError(component, message.toString());
227         } else {
228           // Dagger 1.x scope compatibility requires this be suppress-able.
229           if (!compilerOptions.scopeCycleValidationType().equals(ValidationType.NONE)) {
230             validateDependencyScopeHierarchy(
231                 component, component.typeElement(), new ArrayDeque<>(), new ArrayDeque<>());
232           }
233         }
234       } else {
235         // Scopeless components may not depend on scoped components.
236         if (!scopedDependencies.isEmpty()) {
237           StringBuilder message =
238               new StringBuilder(component.typeElement().getQualifiedName())
239                   .append(" (unscoped) cannot depend on scoped components:\n");
240           appendIndentedComponentsList(message, scopedDependencies);
241           reportComponentError(component, message.toString());
242         }
243       }
244     }
245 
validateModules(ComponentDescriptor component)246     private void validateModules(ComponentDescriptor component) {
247       for (ModuleDescriptor module : component.modules()) {
248         if (module.moduleElement().getModifiers().contains(Modifier.ABSTRACT)) {
249           for (ContributionBinding binding : module.bindings()) {
250             if (binding.requiresModuleInstance()) {
251               report(component).addError(abstractModuleHasInstanceBindingMethodsError(module));
252               break;
253             }
254           }
255         }
256       }
257     }
258 
abstractModuleHasInstanceBindingMethodsError(ModuleDescriptor module)259     private String abstractModuleHasInstanceBindingMethodsError(ModuleDescriptor module) {
260       String methodAnnotations;
261       switch (module.kind()) {
262         case MODULE:
263           methodAnnotations = "@Provides";
264           break;
265         case PRODUCER_MODULE:
266           methodAnnotations = "@Provides or @Produces";
267           break;
268         default:
269           throw new AssertionError(module.kind());
270       }
271       return String.format(
272           "%s is abstract and has instance %s methods. Consider making the methods static or "
273               + "including a non-abstract subclass of the module instead.",
274           module.moduleElement(), methodAnnotations);
275     }
276 
validateCreators(ComponentDescriptor component)277     private void validateCreators(ComponentDescriptor component) {
278       if (!component.creatorDescriptor().isPresent()) {
279         // If no builder, nothing to validate.
280         return;
281       }
282 
283       ComponentCreatorDescriptor creator = component.creatorDescriptor().get();
284       ComponentCreatorMessages messages = ErrorMessages.creatorMessagesFor(creator.annotation());
285 
286       // Requirements for modules and dependencies that the creator can set
287       Set<ComponentRequirement> creatorModuleAndDependencyRequirements =
288           creator.moduleAndDependencyRequirements();
289       // Modules and dependencies the component requires
290       Set<ComponentRequirement> componentModuleAndDependencyRequirements =
291           component.dependenciesAndConcreteModules();
292 
293       // Requirements that the creator can set that don't match any requirements that the component
294       // actually has.
295       Set<ComponentRequirement> inapplicableRequirementsOnCreator =
296           Sets.difference(
297               creatorModuleAndDependencyRequirements, componentModuleAndDependencyRequirements);
298 
299       DeclaredType container = asDeclared(creator.typeElement().asType());
300       if (!inapplicableRequirementsOnCreator.isEmpty()) {
301         Collection<Element> excessElements =
302             Multimaps.filterKeys(
303                     creator.unvalidatedRequirementElements(), in(inapplicableRequirementsOnCreator))
304                 .values();
305         String formatted =
306             excessElements.stream()
307                 .map(element -> formatElement(element, container))
308                 .collect(joining(", ", "[", "]"));
309         report(component)
310             .addError(String.format(messages.extraSetters(), formatted), creator.typeElement());
311       }
312 
313       // Component requirements that the creator must be able to set
314       Set<ComponentRequirement> mustBePassed =
315           Sets.filter(
316               componentModuleAndDependencyRequirements,
317               input -> input.nullPolicy(elements, types).equals(NullPolicy.THROW));
318       // Component requirements that the creator must be able to set, but can't
319       Set<ComponentRequirement> missingRequirements =
320           Sets.difference(mustBePassed, creatorModuleAndDependencyRequirements);
321 
322       if (!missingRequirements.isEmpty()) {
323         report(component)
324             .addError(
325                 String.format(
326                     messages.missingSetters(),
327                     missingRequirements.stream().map(ComponentRequirement::type).collect(toList())),
328                 creator.typeElement());
329       }
330 
331       // Validate that declared creator requirements (modules, dependencies) have unique types.
332       ImmutableSetMultimap<Wrapper<TypeMirror>, Element> declaredRequirementsByType =
333           Multimaps.filterKeys(
334                   creator.unvalidatedRequirementElements(),
335                   creatorModuleAndDependencyRequirements::contains)
336               .entries().stream()
337               .collect(
338                   toImmutableSetMultimap(entry -> entry.getKey().wrappedType(), Entry::getValue));
339       declaredRequirementsByType
340           .asMap()
341           .forEach(
342               (typeWrapper, elementsForType) -> {
343                 if (elementsForType.size() > 1) {
344                   TypeMirror type = typeWrapper.get();
345                   // TODO(cgdecker): Attach this error message to the factory method rather than
346                   // the component type if the elements are factory method parameters AND the
347                   // factory method is defined by the factory type itself and not by a supertype.
348                   report(component)
349                       .addError(
350                           String.format(
351                               messages.multipleSettersForModuleOrDependencyType(),
352                               type,
353                               transform(
354                                   elementsForType, element -> formatElement(element, container))),
355                           creator.typeElement());
356                 }
357               });
358 
359       // TODO(cgdecker): Duplicate binding validation should handle the case of multiple elements
360       // that set the same bound-instance Key, but validating that here would make it fail faster
361       // for subcomponents.
362     }
363 
formatElement(Element element, DeclaredType container)364     private String formatElement(Element element, DeclaredType container) {
365       // TODO(cgdecker): Extract some or all of this to another class?
366       // But note that it does different formatting for parameters than
367       // DaggerElements.elementToString(Element).
368       switch (element.getKind()) {
369         case METHOD:
370           return methodSignatureFormatter.format(
371               MoreElements.asExecutable(element), Optional.of(container));
372         case PARAMETER:
373           return formatParameter(MoreElements.asVariable(element), container);
374         default:
375           // This method shouldn't be called with any other type of element.
376           throw new AssertionError();
377       }
378     }
379 
formatParameter(VariableElement parameter, DeclaredType container)380     private String formatParameter(VariableElement parameter, DeclaredType container) {
381       // TODO(cgdecker): Possibly leave the type (and annotations?) off of the parameters here and
382       // just use their names, since the type will be redundant in the context of the error message.
383       StringJoiner joiner = new StringJoiner(" ");
384       parameter.getAnnotationMirrors().stream().map(Object::toString).forEach(joiner::add);
385       TypeMirror parameterType = resolveParameterType(parameter, container);
386       return joiner
387           .add(stripCommonTypePrefixes(parameterType.toString()))
388           .add(parameter.getSimpleName())
389           .toString();
390     }
391 
resolveParameterType(VariableElement parameter, DeclaredType container)392     private TypeMirror resolveParameterType(VariableElement parameter, DeclaredType container) {
393       ExecutableElement method =
394           MoreElements.asExecutable(parameter.getEnclosingElement());
395       int parameterIndex = method.getParameters().indexOf(parameter);
396 
397       ExecutableType methodType = MoreTypes.asExecutable(types.asMemberOf(container, method));
398       return methodType.getParameterTypes().get(parameterIndex);
399     }
400 
401     /**
402      * Validates that scopes do not participate in a scoping cycle - that is to say, scoped
403      * components are in a hierarchical relationship terminating with Singleton.
404      *
405      * <p>As a side-effect, this means scoped components cannot have a dependency cycle between
406      * themselves, since a component's presence within its own dependency path implies a cyclical
407      * relationship between scopes. However, cycles in component dependencies are explicitly checked
408      * in {@link #validateComponentDependencyHierarchy(ComponentDescriptor)}.
409      */
validateDependencyScopeHierarchy( ComponentDescriptor component, TypeElement dependency, Deque<ImmutableSet<Scope>> scopeStack, Deque<TypeElement> scopedDependencyStack)410     private void validateDependencyScopeHierarchy(
411         ComponentDescriptor component,
412         TypeElement dependency,
413         Deque<ImmutableSet<Scope>> scopeStack,
414         Deque<TypeElement> scopedDependencyStack) {
415       ImmutableSet<Scope> scopes = scopesOf(dependency);
416       if (stackOverlaps(scopeStack, scopes)) {
417         scopedDependencyStack.push(dependency);
418         // Current scope has already appeared in the component chain.
419         StringBuilder message = new StringBuilder();
420         message.append(component.typeElement().getQualifiedName());
421         message.append(" depends on scoped components in a non-hierarchical scope ordering:\n");
422         appendIndentedComponentsList(message, scopedDependencyStack);
423         if (compilerOptions.scopeCycleValidationType().diagnosticKind().isPresent()) {
424           reportComponentItem(
425               compilerOptions.scopeCycleValidationType().diagnosticKind().get(),
426               component,
427               message.toString());
428         }
429         scopedDependencyStack.pop();
430       } else {
431         // TODO(beder): transitively check scopes of production components too.
432         rootComponentAnnotation(dependency)
433             .filter(componentAnnotation -> !componentAnnotation.isProduction())
434             .ifPresent(
435                 componentAnnotation -> {
436                   ImmutableSet<TypeElement> scopedDependencies =
437                       scopedTypesIn(componentAnnotation.dependencies());
438                   if (scopedDependencies.size() == 1) {
439                     // empty can be ignored (base-case), and > 1 is a separately-reported error.
440                     scopeStack.push(scopes);
441                     scopedDependencyStack.push(dependency);
442                     validateDependencyScopeHierarchy(
443                         component,
444                         getOnlyElement(scopedDependencies),
445                         scopeStack,
446                         scopedDependencyStack);
447                     scopedDependencyStack.pop();
448                     scopeStack.pop();
449                   }
450                 }); // else: we skip component dependencies which are not components
451       }
452     }
453 
stackOverlaps(Deque<ImmutableSet<T>> stack, ImmutableSet<T> set)454     private <T> boolean stackOverlaps(Deque<ImmutableSet<T>> stack, ImmutableSet<T> set) {
455       for (ImmutableSet<T> entry : stack) {
456         if (!Sets.intersection(entry, set).isEmpty()) {
457           return true;
458         }
459       }
460       return false;
461     }
462 
463     /** Appends and formats a list of indented component types (with their scope annotations). */
appendIndentedComponentsList(StringBuilder message, Iterable<TypeElement> types)464     private void appendIndentedComponentsList(StringBuilder message, Iterable<TypeElement> types) {
465       for (TypeElement scopedComponent : types) {
466         message.append(INDENT);
467         for (Scope scope : scopesOf(scopedComponent)) {
468           message.append(getReadableSource(scope)).append(' ');
469         }
470         message
471             .append(stripCommonTypePrefixes(scopedComponent.getQualifiedName().toString()))
472             .append('\n');
473       }
474     }
475 
476     /**
477      * Returns a set of type elements containing only those found in the input set that have a
478      * scoping annotation.
479      */
scopedTypesIn(Collection<TypeElement> types)480     private ImmutableSet<TypeElement> scopedTypesIn(Collection<TypeElement> types) {
481       return types.stream().filter(type -> !scopesOf(type).isEmpty()).collect(toImmutableSet());
482     }
483   }
484 }
485