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 androidx.room.compiler.processing.XElementKt.isTypeElement; 20 import static androidx.room.compiler.processing.compat.XConverters.toKS; 21 import static com.google.common.base.Preconditions.checkArgument; 22 import static com.google.common.base.Preconditions.checkNotNull; 23 import static com.google.common.base.Preconditions.checkState; 24 import static dagger.internal.codegen.base.Keys.isValidImplicitProvisionKey; 25 import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey; 26 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; 27 import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; 28 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; 29 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; 30 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; 31 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; 32 import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName; 33 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; 34 import static dagger.internal.codegen.xprocessing.XTypes.nonObjectSuperclass; 35 import static dagger.internal.codegen.xprocessing.XTypes.unwrapType; 36 37 import androidx.room.compiler.processing.XConstructorElement; 38 import androidx.room.compiler.processing.XFieldElement; 39 import androidx.room.compiler.processing.XMessager; 40 import androidx.room.compiler.processing.XMethodElement; 41 import androidx.room.compiler.processing.XProcessingEnv; 42 import androidx.room.compiler.processing.XType; 43 import androidx.room.compiler.processing.XTypeElement; 44 import com.google.common.collect.Maps; 45 import com.google.common.collect.Sets; 46 import com.google.devtools.ksp.symbol.Origin; 47 import com.google.errorprone.annotations.CanIgnoreReturnValue; 48 import com.squareup.javapoet.ClassName; 49 import dagger.Component; 50 import dagger.Provides; 51 import dagger.internal.codegen.base.SourceFileGenerationException; 52 import dagger.internal.codegen.base.SourceFileGenerator; 53 import dagger.internal.codegen.binding.Binding; 54 import dagger.internal.codegen.binding.BindingFactory; 55 import dagger.internal.codegen.binding.InjectBindingRegistry; 56 import dagger.internal.codegen.binding.KeyFactory; 57 import dagger.internal.codegen.binding.MembersInjectionBinding; 58 import dagger.internal.codegen.binding.ProvisionBinding; 59 import dagger.internal.codegen.compileroption.CompilerOptions; 60 import dagger.internal.codegen.javapoet.TypeNames; 61 import dagger.internal.codegen.model.Key; 62 import java.util.ArrayDeque; 63 import java.util.Deque; 64 import java.util.Map; 65 import java.util.Optional; 66 import java.util.Set; 67 import java.util.stream.Stream; 68 import javax.inject.Inject; 69 import javax.inject.Singleton; 70 import javax.tools.Diagnostic.Kind; 71 72 /** 73 * Maintains the collection of provision bindings from {@link Inject} constructors and members 74 * injection bindings from {@link Inject} fields and methods known to the annotation processor. Note 75 * that this registry <b>does not</b> handle any explicit bindings (those from {@link Provides} 76 * methods, {@link Component} dependencies, etc.). 77 */ 78 @Singleton 79 final class InjectBindingRegistryImpl implements InjectBindingRegistry { 80 private final XProcessingEnv processingEnv; 81 private final XMessager messager; 82 private final InjectValidator injectValidator; 83 private final InjectValidator injectValidatorWhenGeneratingCode; 84 private final KeyFactory keyFactory; 85 private final BindingFactory bindingFactory; 86 private final CompilerOptions compilerOptions; 87 88 private final class BindingsCollection<B extends Binding> { 89 private final ClassName factoryClass; 90 private final Map<Key, B> bindingsByKey = Maps.newLinkedHashMap(); 91 private final Deque<B> bindingsRequiringGeneration = new ArrayDeque<>(); 92 private final Set<Key> materializedBindingKeys = Sets.newLinkedHashSet(); 93 BindingsCollection(ClassName factoryClass)94 BindingsCollection(ClassName factoryClass) { 95 this.factoryClass = factoryClass; 96 } 97 generateBindings(SourceFileGenerator<B> generator)98 void generateBindings(SourceFileGenerator<B> generator) throws SourceFileGenerationException { 99 for (B binding = bindingsRequiringGeneration.poll(); 100 binding != null; 101 binding = bindingsRequiringGeneration.poll()) { 102 checkState(!binding.unresolved().isPresent()); 103 XType type = binding.key().type().xprocessing(); 104 if (!isDeclared(type) 105 || injectValidatorWhenGeneratingCode.validate(type.getTypeElement()).isClean()) { 106 generator.generate(binding); 107 } 108 materializedBindingKeys.add(binding.key()); 109 } 110 // Because Elements instantiated across processing rounds are not guaranteed to be equals() to 111 // the logically same element, clear the cache after generating 112 bindingsByKey.clear(); 113 } 114 115 /** Returns a previously cached binding. */ getBinding(Key key)116 B getBinding(Key key) { 117 return bindingsByKey.get(key); 118 } 119 120 /** Caches the binding and generates it if it needs generation. */ tryRegisterBinding(B binding, boolean isCalledFromInjectProcessor)121 void tryRegisterBinding(B binding, boolean isCalledFromInjectProcessor) { 122 if (processingEnv.getBackend() == XProcessingEnv.Backend.KSP) { 123 Origin origin = 124 toKS(closestEnclosingTypeElement(binding.bindingElement().get())).getOrigin(); 125 // If the origin of the element is from a source file in the current compilation unit then 126 // we're guaranteed that the InjectProcessor should run over the element so only generate 127 // the Factory/MembersInjector if we're being called from the InjectProcessor. 128 // 129 // TODO(bcorso): generally, this isn't something we would need to keep track of manually. 130 // However, KSP incremental processing has a bug that will overwrite the cache for the 131 // element if we generate files for it, which can lead to missing generated files from 132 // other processors. See https://github.com/google/dagger/issues/4063 and 133 // https://github.com/google/dagger/issues/4054. Remove this once that bug is fixed. 134 if (!isCalledFromInjectProcessor && (origin == Origin.JAVA || origin == Origin.KOTLIN)) { 135 return; 136 } 137 } 138 tryToCacheBinding(binding); 139 140 @SuppressWarnings("unchecked") 141 B maybeUnresolved = 142 binding.unresolved().isPresent() ? (B) binding.unresolved().get() : binding; 143 tryToGenerateBinding(maybeUnresolved, isCalledFromInjectProcessor); 144 } 145 146 /** 147 * Tries to generate a binding, not generating if it already is generated. For resolved 148 * bindings, this will try to generate the unresolved version of the binding. 149 */ tryToGenerateBinding(B binding, boolean isCalledFromInjectProcessor)150 void tryToGenerateBinding(B binding, boolean isCalledFromInjectProcessor) { 151 if (shouldGenerateBinding(binding)) { 152 bindingsRequiringGeneration.offer(binding); 153 if (compilerOptions.warnIfInjectionFactoryNotGeneratedUpstream() 154 && !isCalledFromInjectProcessor) { 155 messager.printMessage( 156 Kind.NOTE, 157 String.format( 158 "Generating a %s for %s. " 159 + "Prefer to run the dagger processor over that class instead.", 160 factoryClass.simpleName(), 161 // erasure to strip <T> from msgs. 162 erasedTypeName(binding.key().type().xprocessing()))); 163 } 164 } 165 } 166 167 /** Returns true if the binding needs to be generated. */ shouldGenerateBinding(B binding)168 private boolean shouldGenerateBinding(B binding) { 169 if (binding instanceof MembersInjectionBinding) { 170 MembersInjectionBinding membersInjectionBinding = (MembersInjectionBinding) binding; 171 // Empty members injection bindings are special and don't need source files. 172 if (membersInjectionBinding.injectionSites().isEmpty()) { 173 return false; 174 } 175 // Members injectors for classes with no local injection sites and no @Inject 176 // constructor are unused. 177 boolean hasInjectConstructor = 178 !(injectedConstructors(membersInjectionBinding.membersInjectedType()).isEmpty() 179 && assistedInjectedConstructors( 180 membersInjectionBinding.membersInjectedType()).isEmpty()); 181 if (!membersInjectionBinding.hasLocalInjectionSites() && !hasInjectConstructor) { 182 return false; 183 } 184 } 185 return !binding.unresolved().isPresent() 186 && !materializedBindingKeys.contains(binding.key()) 187 && !bindingsRequiringGeneration.contains(binding) 188 && processingEnv.findTypeElement(generatedClassNameForBinding(binding)) == null; 189 } 190 191 /** Caches the binding for future lookups by key. */ tryToCacheBinding(B binding)192 private void tryToCacheBinding(B binding) { 193 // We only cache resolved bindings or unresolved bindings w/o type arguments. 194 // Unresolved bindings w/ type arguments aren't valid for the object graph. 195 if (binding.unresolved().isPresent() 196 || binding.bindingTypeElement().get().getType().getTypeArguments().isEmpty()) { 197 Key key = binding.key(); 198 Binding previousValue = bindingsByKey.put(key, binding); 199 checkState(previousValue == null || binding.equals(previousValue), 200 "couldn't register %s. %s was already registered for %s", 201 binding, previousValue, key); 202 } 203 } 204 } 205 206 private final BindingsCollection<ProvisionBinding> provisionBindings = 207 new BindingsCollection<>(TypeNames.PROVIDER); 208 private final BindingsCollection<MembersInjectionBinding> membersInjectionBindings = 209 new BindingsCollection<>(TypeNames.MEMBERS_INJECTOR); 210 211 @Inject InjectBindingRegistryImpl( XProcessingEnv processingEnv, XMessager messager, InjectValidator injectValidator, KeyFactory keyFactory, BindingFactory bindingFactory, CompilerOptions compilerOptions)212 InjectBindingRegistryImpl( 213 XProcessingEnv processingEnv, 214 XMessager messager, 215 InjectValidator injectValidator, 216 KeyFactory keyFactory, 217 BindingFactory bindingFactory, 218 CompilerOptions compilerOptions) { 219 this.processingEnv = processingEnv; 220 this.messager = messager; 221 this.injectValidator = injectValidator; 222 this.injectValidatorWhenGeneratingCode = injectValidator.whenGeneratingCode(); 223 this.keyFactory = keyFactory; 224 this.bindingFactory = bindingFactory; 225 this.compilerOptions = compilerOptions; 226 } 227 228 // TODO(dpb): make the SourceFileGenerators fields so they don't have to be passed in 229 @Override generateSourcesForRequiredBindings( SourceFileGenerator<ProvisionBinding> factoryGenerator, SourceFileGenerator<MembersInjectionBinding> membersInjectorGenerator)230 public void generateSourcesForRequiredBindings( 231 SourceFileGenerator<ProvisionBinding> factoryGenerator, 232 SourceFileGenerator<MembersInjectionBinding> membersInjectorGenerator) 233 throws SourceFileGenerationException { 234 provisionBindings.generateBindings(factoryGenerator); 235 membersInjectionBindings.generateBindings(membersInjectorGenerator); 236 } 237 238 @Override tryRegisterInjectConstructor( XConstructorElement constructorElement)239 public Optional<ProvisionBinding> tryRegisterInjectConstructor( 240 XConstructorElement constructorElement) { 241 return tryRegisterConstructor( 242 constructorElement, 243 Optional.empty(), 244 /* isCalledFromInjectProcessor= */ true); 245 } 246 247 @CanIgnoreReturnValue tryRegisterConstructor( XConstructorElement constructorElement, Optional<XType> resolvedType, boolean isCalledFromInjectProcessor)248 private Optional<ProvisionBinding> tryRegisterConstructor( 249 XConstructorElement constructorElement, 250 Optional<XType> resolvedType, 251 boolean isCalledFromInjectProcessor) { 252 XTypeElement typeElement = constructorElement.getEnclosingElement(); 253 254 // Validating here shouldn't have a performance penalty because the validator caches its reports 255 ValidationReport report = injectValidator.validate(typeElement); 256 report.printMessagesTo(messager); 257 if (!report.isClean()) { 258 return Optional.empty(); 259 } 260 261 XType type = typeElement.getType(); 262 Key key = keyFactory.forInjectConstructorWithResolvedType(type); 263 ProvisionBinding cachedBinding = provisionBindings.getBinding(key); 264 if (cachedBinding != null) { 265 return Optional.of(cachedBinding); 266 } 267 268 ProvisionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType); 269 provisionBindings.tryRegisterBinding(binding, isCalledFromInjectProcessor); 270 if (!binding.injectionSites().isEmpty()) { 271 tryRegisterMembersInjectedType(typeElement, resolvedType, isCalledFromInjectProcessor); 272 } 273 return Optional.of(binding); 274 } 275 276 @Override tryRegisterInjectField(XFieldElement fieldElement)277 public Optional<MembersInjectionBinding> tryRegisterInjectField(XFieldElement fieldElement) { 278 // TODO(b/204116636): Add a test for this once we're able to test kotlin sources. 279 // TODO(b/204208307): Add validation for KAPT to test if this came from a top-level field. 280 if (!isTypeElement(fieldElement.getEnclosingElement())) { 281 messager.printMessage( 282 Kind.ERROR, 283 "@Inject fields must be enclosed in a type.", 284 fieldElement); 285 } 286 return tryRegisterMembersInjectedType( 287 asTypeElement(fieldElement.getEnclosingElement()), 288 Optional.empty(), 289 /* isCalledFromInjectProcessor= */ true); 290 } 291 292 @Override tryRegisterInjectMethod(XMethodElement methodElement)293 public Optional<MembersInjectionBinding> tryRegisterInjectMethod(XMethodElement methodElement) { 294 // TODO(b/204116636): Add a test for this once we're able to test kotlin sources. 295 // TODO(b/204208307): Add validation for KAPT to test if this came from a top-level method. 296 if (!isTypeElement(methodElement.getEnclosingElement())) { 297 messager.printMessage( 298 Kind.ERROR, 299 "@Inject methods must be enclosed in a type.", 300 methodElement); 301 } 302 return tryRegisterMembersInjectedType( 303 asTypeElement(methodElement.getEnclosingElement()), 304 Optional.empty(), 305 /* isCalledFromInjectProcessor= */ true); 306 } 307 308 @CanIgnoreReturnValue tryRegisterMembersInjectedType( XTypeElement typeElement, Optional<XType> resolvedType, boolean isCalledFromInjectProcessor)309 private Optional<MembersInjectionBinding> tryRegisterMembersInjectedType( 310 XTypeElement typeElement, 311 Optional<XType> resolvedType, 312 boolean isCalledFromInjectProcessor) { 313 // Validating here shouldn't have a performance penalty because the validator caches its reports 314 ValidationReport report = injectValidator.validateForMembersInjection(typeElement); 315 report.printMessagesTo(messager); 316 if (!report.isClean()) { 317 return Optional.empty(); 318 } 319 320 XType type = typeElement.getType(); 321 Key key = keyFactory.forInjectConstructorWithResolvedType(type); 322 MembersInjectionBinding cachedBinding = membersInjectionBindings.getBinding(key); 323 if (cachedBinding != null) { 324 return Optional.of(cachedBinding); 325 } 326 327 MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType); 328 membersInjectionBindings.tryRegisterBinding(binding, isCalledFromInjectProcessor); 329 for (Optional<XType> supertype = nonObjectSuperclass(type); 330 supertype.isPresent(); 331 supertype = nonObjectSuperclass(supertype.get())) { 332 getOrFindMembersInjectionBinding(keyFactory.forMembersInjectedType(supertype.get())); 333 } 334 return Optional.of(binding); 335 } 336 337 @CanIgnoreReturnValue 338 @Override getOrFindProvisionBinding(Key key)339 public Optional<ProvisionBinding> getOrFindProvisionBinding(Key key) { 340 checkNotNull(key); 341 if (!isValidImplicitProvisionKey(key)) { 342 return Optional.empty(); 343 } 344 ProvisionBinding binding = provisionBindings.getBinding(key); 345 if (binding != null) { 346 return Optional.of(binding); 347 } 348 349 XType type = key.type().xprocessing(); 350 XTypeElement element = type.getTypeElement(); 351 352 ValidationReport report = injectValidator.validate(element); 353 report.printMessagesTo(messager); 354 if (!report.isClean()) { 355 return Optional.empty(); 356 } 357 358 return Stream.concat( 359 injectedConstructors(element).stream(), 360 assistedInjectedConstructors(element).stream()) 361 // We're guaranteed that there's at most 1 @Inject constructors from above validation. 362 .collect(toOptional()) 363 .flatMap( 364 constructor -> 365 tryRegisterConstructor( 366 constructor, 367 Optional.of(type), 368 /* isCalledFromInjectProcessor= */ false)); 369 } 370 371 @CanIgnoreReturnValue 372 @Override getOrFindMembersInjectionBinding(Key key)373 public Optional<MembersInjectionBinding> getOrFindMembersInjectionBinding(Key key) { 374 checkNotNull(key); 375 // TODO(gak): is checking the kind enough? 376 checkArgument(isValidMembersInjectionKey(key)); 377 MembersInjectionBinding binding = membersInjectionBindings.getBinding(key); 378 if (binding != null) { 379 return Optional.of(binding); 380 } 381 return tryRegisterMembersInjectedType( 382 key.type().xprocessing().getTypeElement(), 383 Optional.of(key.type().xprocessing()), 384 /* isCalledFromInjectProcessor= */ false); 385 } 386 387 @Override getOrFindMembersInjectorProvisionBinding(Key key)388 public Optional<ProvisionBinding> getOrFindMembersInjectorProvisionBinding(Key key) { 389 if (!isValidMembersInjectionKey(key)) { 390 return Optional.empty(); 391 } 392 Key membersInjectionKey = 393 keyFactory.forMembersInjectedType(unwrapType(key.type().xprocessing())); 394 return getOrFindMembersInjectionBinding(membersInjectionKey) 395 .map(binding -> bindingFactory.membersInjectorBinding(key, binding)); 396 } 397 } 398