| // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.shaking; |
| |
| import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX; |
| import static com.android.tools.r8.utils.collections.IdentityHashSetFromMap.newProgramDerivedContextSet; |
| |
| import com.android.tools.r8.androidapi.CovariantReturnTypeMethods; |
| import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic; |
| import com.android.tools.r8.diagnostic.internal.DefinitionContextUtils; |
| import com.android.tools.r8.diagnostic.internal.MissingClassInfoImpl; |
| import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl; |
| import com.android.tools.r8.errors.DesugarDiagnostic; |
| import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexReference; |
| import com.android.tools.r8.graph.DexString; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.ProgramDerivedContext; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle; |
| import com.android.tools.r8.utils.SetUtils; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| import java.util.Collection; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| |
| public class MissingClasses { |
| |
| private final Set<DexType> missingClasses; |
| |
| private MissingClasses(Set<DexType> missingClasses) { |
| this.missingClasses = missingClasses; |
| } |
| |
| public Builder builder() { |
| return new Builder(missingClasses); |
| } |
| |
| public static MissingClasses empty() { |
| return new MissingClasses(Sets.newIdentityHashSet()); |
| } |
| |
| public void forEach(Consumer<DexType> missingClassConsumer) { |
| missingClasses.forEach(missingClassConsumer); |
| } |
| |
| public boolean contains(DexType type) { |
| return missingClasses.contains(type); |
| } |
| |
| public static class Builder { |
| |
| private final Set<DexType> alreadyMissingClasses; |
| private final Map<DexType, Set<ProgramDerivedContext>> newMissingClasses = |
| new IdentityHashMap<>(); |
| |
| // Set of missing types that are not to be reported as missing. This does not hide reports |
| // if the same type is in newMissingClasses in which case it is reported regardless. |
| private final Set<DexType> newIgnoredMissingClasses = Sets.newIdentityHashSet(); |
| |
| private Builder(Set<DexType> alreadyMissingClasses) { |
| this.alreadyMissingClasses = alreadyMissingClasses; |
| } |
| |
| public void addNewMissingClass(DexType type, ProgramDerivedContext context) { |
| assert context != null; |
| assert context.getContext().getContextType() != type; |
| if (!alreadyMissingClasses.contains(type)) { |
| newMissingClasses |
| .computeIfAbsent(type, ignore -> newProgramDerivedContextSet()) |
| .add(context); |
| } |
| } |
| |
| public void addNewMissingClassWithDesugarDiagnostic( |
| DexType type, ProgramDerivedContext context, DesugarDiagnostic diagnostic) { |
| // TODO(b/175659048): At this point we just throw out the diagnostic and report the |
| // missing classes only. We should instead report the most specific diagnostic as the |
| // information about missing for desugar is strictly more valuable than just missing. |
| // Note that we should have deterministic reporting and so we should collect all of the |
| // contexts for which desugaring needed the type and report all of those in the same |
| // way as we do for missing classes. |
| addNewMissingClass(type, context); |
| } |
| |
| public void legacyAddNewMissingClass(DexType type) { |
| if (!alreadyMissingClasses.contains(type)) { |
| // The legacy reporting is context insensitive, so we just use an empty set of contexts. |
| newMissingClasses.computeIfAbsent(type, ignore -> newProgramDerivedContextSet()); |
| } |
| } |
| |
| @Deprecated |
| public Builder legacyAddNewMissingClasses(Collection<DexType> types) { |
| types.forEach(this::legacyAddNewMissingClass); |
| return this; |
| } |
| |
| public void ignoreNewMissingClass(DexType type) { |
| newIgnoredMissingClasses.add(type); |
| } |
| |
| public boolean contains(DexType type) { |
| return alreadyMissingClasses.contains(type) |
| || newMissingClasses.containsKey(type) |
| || newIgnoredMissingClasses.contains(type); |
| } |
| |
| Builder removeAlreadyMissingClasses(Iterable<DexType> types) { |
| for (DexType type : types) { |
| alreadyMissingClasses.remove(type); |
| } |
| return this; |
| } |
| |
| public MissingClasses assertNoMissingClasses(AppView<?> appView) { |
| assert getMissingClassesToBeReported(appView, clazz -> ImmutableSet.of(clazz.getType())) |
| .isEmpty(); |
| return build(); |
| } |
| |
| public MissingClasses reportMissingClasses( |
| AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) { |
| Map<DexType, Set<ProgramDerivedContext>> missingClassesToBeReported = |
| getMissingClassesToBeReported(appView, synthesizingContextOracle); |
| if (!missingClassesToBeReported.isEmpty()) { |
| MissingDefinitionsDiagnostic diagnostic = createDiagnostic(missingClassesToBeReported); |
| if (appView.options().ignoreMissingClasses) { |
| appView.reporter().warning(diagnostic); |
| } else { |
| appView.reporter().error(diagnostic); |
| } |
| } |
| appView.reporter().failIfPendingErrors(); |
| return build(); |
| } |
| |
| private MissingDefinitionsDiagnostic createDiagnostic( |
| Map<DexType, Set<ProgramDerivedContext>> missingClassesToBeReported) { |
| MissingDefinitionsDiagnosticImpl.Builder diagnosticBuilder = |
| MissingDefinitionsDiagnosticImpl.builder(); |
| missingClassesToBeReported.forEach( |
| (missingClass, programDerivedContexts) -> { |
| MissingClassInfoImpl.Builder missingClassInfoBuilder = |
| MissingClassInfoImpl.builder().setClass(missingClass.asClassReference()); |
| for (ProgramDerivedContext programDerivedContext : programDerivedContexts) { |
| missingClassInfoBuilder.addReferencedFromContext( |
| DefinitionContextUtils.create(programDerivedContext)); |
| } |
| diagnosticBuilder.addMissingDefinitionInfo(missingClassInfoBuilder.build()); |
| }); |
| return diagnosticBuilder.build(); |
| } |
| |
| private void rewriteMissingClassContexts( |
| AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) { |
| Iterator<Entry<DexType, Set<ProgramDerivedContext>>> newMissingClassesIterator = |
| newMissingClasses.entrySet().iterator(); |
| while (newMissingClassesIterator.hasNext()) { |
| Entry<DexType, Set<ProgramDerivedContext>> entry = newMissingClassesIterator.next(); |
| entry.setValue( |
| rewriteMissingClassContextsForSingleMissingClass( |
| appView, entry.getValue(), synthesizingContextOracle)); |
| } |
| } |
| |
| private static Set<ProgramDerivedContext> rewriteMissingClassContextsForSingleMissingClass( |
| AppView<?> appView, |
| Set<ProgramDerivedContext> contexts, |
| SynthesizingContextOracle synthesizingContextOracle) { |
| if (contexts.isEmpty()) { |
| // Legacy reporting does not have any contexts. |
| return contexts; |
| } |
| |
| Set<ProgramDerivedContext> rewrittenContexts = newProgramDerivedContextSet(); |
| for (ProgramDerivedContext context : contexts) { |
| if (!context.isProgramContext()) { |
| rewrittenContexts.add(context); |
| continue; |
| } |
| |
| DexProgramClass clazz = context.getContext().asProgramDefinition().getContextClass(); |
| if (!appView.getSyntheticItems().isSyntheticClass(clazz)) { |
| rewrittenContexts.add(context); |
| continue; |
| } |
| |
| // Rewrite the synthetic context to its synthesizing contexts. |
| Set<DexReference> synthesizingContextReferences = |
| appView |
| .getSyntheticItems() |
| .getSynthesizingContextReferences(clazz, synthesizingContextOracle); |
| assert synthesizingContextReferences != null; |
| assert !synthesizingContextReferences.isEmpty(); |
| for (DexReference synthesizingContextReference : synthesizingContextReferences) { |
| if (synthesizingContextReference.isDexMethod()) { |
| DexProgramClass synthesizingContextHolder = |
| appView |
| .definitionFor(synthesizingContextReference.getContextType()) |
| .asProgramClass(); |
| ProgramMethod synthesizingContext = |
| synthesizingContextHolder.lookupProgramMethod( |
| synthesizingContextReference.asDexMethod()); |
| if (synthesizingContext != null) { |
| rewrittenContexts.add(synthesizingContext); |
| } else { |
| // The synthesizing context no longer exists. It must have been forcefully moved due |
| // to desugaring. For now we report the holder of the synthesizing context as the |
| // origin of the missing class reference. |
| assert synthesizingContextHolder.isInterface(); |
| rewrittenContexts.add(synthesizingContextHolder); |
| } |
| } else if (synthesizingContextReference.isDexType()) { |
| DexProgramClass synthesizingClass = |
| appView.definitionFor(synthesizingContextReference.asDexType()).asProgramClass(); |
| rewrittenContexts.add(synthesizingClass); |
| } else { |
| assert false |
| : "Unexpected synthesizing context: " |
| + synthesizingContextReference.toSourceString(); |
| } |
| } |
| } |
| return rewrittenContexts; |
| } |
| |
| private Map<DexType, Set<ProgramDerivedContext>> getMissingClassesToBeReported( |
| AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) { |
| // Rewrite synthetic program contexts into their synthesizing contexts, to avoid reporting |
| // any synthetic contexts. |
| rewriteMissingClassContexts(appView, synthesizingContextOracle); |
| Predicate<DexType> allowedMissingClassesPredicate = |
| getIsAllowedMissingClassesPredicate(appView); |
| Map<DexType, Set<ProgramDerivedContext>> missingClassesToBeReported = |
| new IdentityHashMap<>(newMissingClasses.size()); |
| newMissingClasses.forEach( |
| (missingClass, contexts) -> { |
| // Don't report "allowed" missing classes (e.g., classes matched by -dontwarn). |
| if (allowedMissingClassesPredicate.test(missingClass)) { |
| return; |
| } |
| |
| // Remove all contexts that are matched by a -dontwarn rule (a missing class should not |
| // be reported if it os only referenced from contexts that are matched by a -dontwarn). |
| contexts.removeIf( |
| context -> |
| appView |
| .getDontWarnConfiguration() |
| .matches(context.getContext().getContextType())); |
| |
| // If there are any contexts not matched by a -dontwarn rule, then report. |
| if (!contexts.isEmpty()) { |
| missingClassesToBeReported.put(missingClass, contexts); |
| } |
| }); |
| return missingClassesToBeReported; |
| } |
| |
| private static Predicate<DexType> getIsAllowedMissingClassesPredicate(AppView<?> appView) { |
| Set<DexType> allowedMissingClasses = getAllowedMissingClasses(appView); |
| Predicate<DexType> compilerSynthesizedAllowingMissingClassPredicate = |
| getIsCompilerSynthesizedAllowedMissingClassesPredicate(appView); |
| DontWarnConfiguration dontWarnConfiguration = appView.getDontWarnConfiguration(); |
| return type -> |
| allowedMissingClasses.contains(type) |
| || compilerSynthesizedAllowingMissingClassPredicate.test(type) |
| || dontWarnConfiguration.matches(type); |
| } |
| |
| private static Set<DexType> getAllowedMissingClasses(AppView<?> appView) { |
| DexItemFactory dexItemFactory = appView.dexItemFactory(); |
| ImmutableSet.Builder<DexType> allowedMissingClasses = |
| ImmutableSet.<DexType>builder() |
| .add( |
| dexItemFactory.annotationDefault, |
| dexItemFactory.annotationMethodParameters, |
| dexItemFactory.annotationSourceDebugExtension, |
| dexItemFactory.annotationSynthesizedClass, |
| dexItemFactory.annotationThrows, |
| dexItemFactory.serializedLambdaType, |
| dexItemFactory.unsafeType); |
| appView |
| .options() |
| .machineDesugaredLibrarySpecification |
| .getCustomConversions() |
| .forEach( |
| (type, conversions) -> { |
| addWithRewrittenType( |
| allowedMissingClasses, conversions.getFrom().getHolderType(), appView); |
| addWithRewrittenType( |
| allowedMissingClasses, conversions.getTo().getHolderType(), appView); |
| }); |
| CovariantReturnTypeMethods.registerMethodsWithCovariantReturnType( |
| dexItemFactory, |
| method -> method.getReferencedTypes().forEach(allowedMissingClasses::add)); |
| return allowedMissingClasses.build(); |
| } |
| |
| private static void addWithRewrittenType( |
| ImmutableSet.Builder<DexType> builder, DexType type, AppView<?> appView) { |
| builder.add(type); |
| DexType rewrittenType = appView.typeRewriter.rewrittenType(type, appView); |
| if (rewrittenType != null) { |
| builder.add(rewrittenType); |
| } |
| } |
| |
| private static Predicate<DexType> getIsCompilerSynthesizedAllowedMissingClassesPredicate( |
| AppView<?> appView) { |
| DexItemFactory dexItemFactory = appView.dexItemFactory(); |
| DexString vivifiedClassNamePrefix = dexItemFactory.createString(DESCRIPTOR_VIVIFIED_PREFIX); |
| return type -> { |
| DexString descriptor = type.getDescriptor(); |
| return descriptor.startsWith(vivifiedClassNamePrefix); |
| }; |
| } |
| |
| |
| |
| /** Intentionally private, use {@link Builder#reportMissingClasses(AppView)}. */ |
| private MissingClasses build() { |
| // Return the new set of missing classes. |
| // |
| // We also add newIgnoredMissingClasses to newMissingClasses to be able to assert that we have |
| // a closed world after the first round of tree shaking: we should never lookup a class that |
| // was not live or missing during the first round of tree shaking. |
| // See also AppInfoWithLiveness.definitionFor(). |
| // |
| // Note: At this point, all missing classes in newMissingClasses have already been reported. |
| // Thus adding newIgnoredMissingClasses to newMissingClasses will not lead to reports for the |
| // classes in newIgnoredMissingClasses. |
| return new MissingClasses( |
| SetUtils.newIdentityHashSet( |
| alreadyMissingClasses, newMissingClasses.keySet(), newIgnoredMissingClasses)); |
| } |
| |
| public boolean wasAlreadyMissing(DexType type) { |
| return alreadyMissingClasses.contains(type); |
| } |
| } |
| } |