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