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 androidx.room.compiler.processing.XElementKt.isField; 20 import static androidx.room.compiler.processing.XElementKt.isTypeElement; 21 import static dagger.internal.codegen.base.FrameworkTypes.isDisallowedType; 22 import static dagger.internal.codegen.base.FrameworkTypes.isFrameworkType; 23 import static dagger.internal.codegen.base.FrameworkTypes.isMapValueFrameworkType; 24 import static dagger.internal.codegen.base.RequestKinds.extractKeyType; 25 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType; 26 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedInjectionType; 27 import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; 28 import static dagger.internal.codegen.xprocessing.XElements.asField; 29 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; 30 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 31 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; 32 import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType; 33 import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; 34 import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; 35 36 import androidx.room.compiler.processing.XAnnotation; 37 import androidx.room.compiler.processing.XElement; 38 import androidx.room.compiler.processing.XFieldElement; 39 import androidx.room.compiler.processing.XProcessingEnv; 40 import androidx.room.compiler.processing.XType; 41 import androidx.room.compiler.processing.XTypeElement; 42 import androidx.room.compiler.processing.XVariableElement; 43 import com.google.common.collect.ImmutableSet; 44 import dagger.internal.codegen.base.FrameworkTypes; 45 import dagger.internal.codegen.base.MapType; 46 import dagger.internal.codegen.base.RequestKinds; 47 import dagger.internal.codegen.binding.InjectionAnnotations; 48 import dagger.internal.codegen.javapoet.TypeNames; 49 import dagger.internal.codegen.kotlin.KotlinMetadataUtil; 50 import dagger.internal.codegen.model.RequestKind; 51 import dagger.internal.codegen.xprocessing.XTypes; 52 import java.util.Optional; 53 import javax.inject.Inject; 54 55 /** Validation for dependency requests. */ 56 final class DependencyRequestValidator { 57 private final XProcessingEnv processingEnv; 58 private final MembersInjectionValidator membersInjectionValidator; 59 private final InjectionAnnotations injectionAnnotations; 60 private final KotlinMetadataUtil metadataUtil; 61 62 @Inject DependencyRequestValidator( XProcessingEnv processingEnv, MembersInjectionValidator membersInjectionValidator, InjectionAnnotations injectionAnnotations, KotlinMetadataUtil metadataUtil)63 DependencyRequestValidator( 64 XProcessingEnv processingEnv, 65 MembersInjectionValidator membersInjectionValidator, 66 InjectionAnnotations injectionAnnotations, 67 KotlinMetadataUtil metadataUtil) { 68 this.processingEnv = processingEnv; 69 this.membersInjectionValidator = membersInjectionValidator; 70 this.injectionAnnotations = injectionAnnotations; 71 this.metadataUtil = metadataUtil; 72 } 73 74 /** 75 * Adds an error if the given dependency request has more than one qualifier annotation or is a 76 * non-instance request with a wildcard type. 77 */ validateDependencyRequest( ValidationReport.Builder report, XElement requestElement, XType requestType)78 void validateDependencyRequest( 79 ValidationReport.Builder report, XElement requestElement, XType requestType) { 80 if (requestElement.hasAnnotation(TypeNames.ASSISTED)) { 81 // Don't validate assisted parameters. These are not dependency requests. 82 return; 83 } 84 if (missingQualifierMetadata(requestElement)) { 85 report.addError( 86 "Unable to read annotations on an injected Kotlin property. " 87 + "The Dagger compiler must also be applied to any project containing @Inject " 88 + "properties.", 89 requestElement); 90 91 // Skip any further validation if we don't have valid metadata for a type that needs it. 92 return; 93 } 94 95 new Validator(report, requestElement, requestType).validate(); 96 } 97 98 /** 99 * Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers. 100 * 101 * <p>See https://youtrack.jetbrains.com/issue/KT-34684. 102 */ missingQualifierMetadata(XElement requestElement)103 private boolean missingQualifierMetadata(XElement requestElement) { 104 if (isField(requestElement)) { 105 XFieldElement fieldElement = asField(requestElement); 106 // static/top-level injected fields are not supported, 107 // so no need to get qualifier from kotlin metadata 108 if (!fieldElement.isStatic() 109 && isTypeElement(fieldElement.getEnclosingElement()) 110 && metadataUtil.hasMetadata(fieldElement) 111 && metadataUtil.isMissingSyntheticPropertyForAnnotations(fieldElement)) { 112 Optional<XTypeElement> membersInjector = 113 Optional.ofNullable( 114 processingEnv.findTypeElement( 115 membersInjectorNameForType(asTypeElement(fieldElement.getEnclosingElement())))); 116 return !membersInjector.isPresent(); 117 } 118 } 119 return false; 120 } 121 122 private final class Validator { 123 private final ValidationReport.Builder report; 124 private final XElement requestElement; 125 private final XType requestType; 126 private final ImmutableSet<XAnnotation> qualifiers; 127 Validator(ValidationReport.Builder report, XElement requestElement, XType requestType)128 Validator(ValidationReport.Builder report, XElement requestElement, XType requestType) { 129 this.report = report; 130 this.requestElement = requestElement; 131 this.requestType = requestType; 132 this.qualifiers = injectionAnnotations.getQualifiers(requestElement); 133 } 134 validate()135 void validate() { 136 checkQualifiers(); 137 checkType(); 138 } 139 checkQualifiers()140 private void checkQualifiers() { 141 if (qualifiers.size() > 1) { 142 for (XAnnotation qualifier : qualifiers) { 143 report.addError( 144 "A single dependency request may not use more than one @Qualifier", 145 requestElement, 146 qualifier); 147 } 148 } 149 } 150 checkType()151 private void checkType() { 152 if (isFrameworkType(requestType) && isRawParameterizedType(requestType)) { 153 report.addError( 154 "Dagger does not support injecting raw type: " + XTypes.toStableString(requestType), 155 requestElement); 156 // If the requested type is a raw framework type then skip the remaining checks as they 157 // will just be noise. 158 return; 159 } 160 if (isDisallowedType(requestType)) { 161 report.addError( 162 "Dagger disallows injecting the type: " + XTypes.toStableString(requestType), 163 requestElement); 164 // If the requested type is a disallowed type then skip the remaining checks as they 165 // will just be noise. 166 return; 167 } 168 XType keyType = extractKeyType(requestType); 169 if (qualifiers.isEmpty() && isDeclared(keyType)) { 170 XTypeElement typeElement = keyType.getTypeElement(); 171 if (isAssistedInjectionType(typeElement)) { 172 report.addError( 173 "Dagger does not support injecting @AssistedInject type, " 174 + XTypes.toStableString(requestType) 175 + ". Did you mean to inject its assisted factory type instead?", 176 requestElement); 177 } 178 RequestKind requestKind = RequestKinds.getRequestKind(requestType); 179 if (!(requestKind == RequestKind.INSTANCE || requestKind == RequestKind.PROVIDER) 180 && isAssistedFactoryType(typeElement)) { 181 report.addError( 182 "Dagger does not support injecting Lazy<T>, Producer<T>, " 183 + "or Produced<T> when T is an @AssistedFactory-annotated type such as " 184 + XTypes.toStableString(keyType), 185 requestElement); 186 } 187 } 188 if (isWildcard(keyType)) { 189 // TODO(ronshapiro): Explore creating this message using RequestKinds. 190 report.addError( 191 "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, " 192 + "or Produced<T> when T is a wildcard type such as " 193 + XTypes.toStableString(keyType), 194 requestElement); 195 } 196 if (isTypeOf(keyType, TypeNames.MEMBERS_INJECTOR)) { 197 if (keyType.getTypeArguments().isEmpty()) { 198 report.addError("Cannot inject a raw MembersInjector", requestElement); 199 } else { 200 report.addSubreport( 201 membersInjectionValidator.validateMembersInjectionRequest( 202 requestElement, keyType.getTypeArguments().get(0))); 203 } 204 } 205 if (MapType.isMap(keyType)) { 206 MapType mapType = MapType.from(keyType); 207 if (!mapType.isRawType()) { 208 XType valueType = mapType.valueType(); 209 if (isMapValueFrameworkType(valueType) && isRawParameterizedType(valueType)) { 210 report.addError( 211 "Dagger does not support injecting maps of raw framework types: " 212 + XTypes.toStableString(requestType), 213 requestElement); 214 } 215 if (isDisallowedType(valueType)) { 216 report.addError( 217 "Dagger does not support injecting maps of disallowed types: " 218 + XTypes.toStableString(requestType), 219 requestElement); 220 } 221 } 222 } 223 } 224 } 225 226 /** 227 * Adds an error if the given dependency request is for a {@link dagger.producers.Producer} or 228 * {@link dagger.producers.Produced}. 229 * 230 * <p>Only call this when processing a provision binding. 231 */ 232 // TODO(dpb): Should we disallow Producer entry points in non-production components? checkNotProducer(ValidationReport.Builder report, XVariableElement requestElement)233 void checkNotProducer(ValidationReport.Builder report, XVariableElement requestElement) { 234 XType requestType = requestElement.getType(); 235 if (FrameworkTypes.isProducerType(requestType)) { 236 report.addError( 237 String.format( 238 "%s may only be injected in @Produces methods", 239 getSimpleName(requestType.getTypeElement())), 240 requestElement); 241 } 242 } 243 } 244