| // Copyright (c) 2017, 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. |
| // Copyright (c) 2016, 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.graph; |
| |
| import static com.android.tools.r8.graph.ClassResolutionResult.NoResolutionResult.noResult; |
| |
| import com.android.tools.r8.DataResourceProvider; |
| import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses; |
| import com.android.tools.r8.graph.lens.GraphLens; |
| import com.android.tools.r8.naming.ClassNameMapper; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.Timing; |
| import com.google.common.collect.ImmutableCollection; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Sets; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| |
| public class DirectMappedDexApplication extends DexApplication { |
| |
| // Mapping from code objects to their encoded-method owner. Used for asserting unique ownership |
| // and debugging purposes. |
| private final Map<Code, DexEncodedMethod> codeOwners = new IdentityHashMap<>(); |
| |
| // Unmodifiable mapping of all types to their definitions. |
| private final ImmutableMap<DexType, ProgramOrClasspathClass> programOrClasspathClasses; |
| private final ImmutableMap<DexType, DexLibraryClass> libraryClasses; |
| |
| // Collections of different types for iteration. |
| private final ImmutableCollection<DexProgramClass> programClasses; |
| private final ImmutableCollection<DexClasspathClass> classpathClasses; |
| |
| private DirectMappedDexApplication( |
| ClassNameMapper proguardMap, |
| DexApplicationReadFlags flags, |
| ImmutableMap<DexType, ProgramOrClasspathClass> programOrClasspathClasses, |
| ImmutableMap<DexType, DexLibraryClass> libraryClasses, |
| ImmutableCollection<DexProgramClass> programClasses, |
| ImmutableCollection<DexClasspathClass> classpathClasses, |
| ImmutableList<DataResourceProvider> dataResourceProviders, |
| InternalOptions options, |
| Timing timing) { |
| super(proguardMap, flags, dataResourceProviders, options, timing); |
| this.programOrClasspathClasses = programOrClasspathClasses; |
| this.libraryClasses = libraryClasses; |
| this.programClasses = programClasses; |
| this.classpathClasses = classpathClasses; |
| } |
| |
| public Collection<DexClasspathClass> classpathClasses() { |
| return classpathClasses; |
| } |
| |
| @Override |
| Collection<DexProgramClass> programClasses() { |
| return programClasses; |
| } |
| |
| @Override |
| public void forEachProgramType(Consumer<DexType> consumer) { |
| programClasses.forEach(clazz -> consumer.accept(clazz.type)); |
| } |
| |
| @Override |
| public void forEachLibraryType(Consumer<DexType> consumer) { |
| libraryClasses.forEach((type, clazz) -> consumer.accept(type)); |
| } |
| |
| public Collection<DexLibraryClass> libraryClasses() { |
| return libraryClasses.values(); |
| } |
| |
| @Override |
| public ClassResolutionResult contextIndependentDefinitionForWithResolutionResult(DexType type) { |
| assert type.isClassType() : "Cannot lookup definition for type: " + type; |
| DexLibraryClass libraryClass = libraryClasses.get(type); |
| ProgramOrClasspathClass programOrClasspathClass = programOrClasspathClasses.get(type); |
| if (libraryClass == null && programOrClasspathClass == null) { |
| return noResult(); |
| } else if (libraryClass != null && programOrClasspathClass == null) { |
| return libraryClass; |
| } else if (libraryClass == null) { |
| return programOrClasspathClass.asDexClass(); |
| } else { |
| return ClassResolutionResult.builder().add(libraryClass).add(programOrClasspathClass).build(); |
| } |
| } |
| |
| @Override |
| public DexClass definitionFor(DexType type) { |
| assert type.isClassType() : "Cannot lookup definition for type: " + type; |
| ProgramOrClasspathClass programOrClasspathClass = programOrClasspathClasses.get(type); |
| if (programOrClasspathClass != null && programOrClasspathClass.isProgramClass()) { |
| return programOrClasspathClass.asDexClass(); |
| } |
| DexLibraryClass libraryClass = libraryClasses.get(type); |
| return libraryClass != null |
| ? libraryClass |
| : (programOrClasspathClass == null ? null : programOrClasspathClass.asDexClass()); |
| } |
| |
| @Override |
| public DexProgramClass programDefinitionFor(DexType type) { |
| ProgramOrClasspathClass programOrClasspathClass = programOrClasspathClasses.get(type); |
| return programOrClasspathClass == null ? null : programOrClasspathClass.asProgramClass(); |
| } |
| |
| @Override |
| public Builder builder() { |
| return new Builder(this); |
| } |
| |
| @Override |
| public DirectMappedDexApplication toDirect() { |
| return this; |
| } |
| |
| @Override |
| public boolean isDirect() { |
| return true; |
| } |
| |
| @Override |
| public DirectMappedDexApplication asDirect() { |
| return this; |
| } |
| |
| @Override |
| public String toString() { |
| return "DexApplication (direct)"; |
| } |
| |
| public boolean verifyWithLens(DirectMappedDexApplication beforeLensApplication, GraphLens lens) { |
| assert mappingIsValid(beforeLensApplication.programClasses(), lens); |
| assert verifyCodeObjectsOwners(); |
| return true; |
| } |
| |
| @SuppressWarnings("ReferenceEquality") |
| private boolean mappingIsValid( |
| Collection<DexProgramClass> classesBeforeLensApplication, GraphLens lens) { |
| // The lens might either map to a different type that is already present in the application |
| // (e.g. relinking a type) or it might encode a type that was renamed, in which case the |
| // original type will point to a definition that was renamed. |
| for (DexProgramClass clazz : classesBeforeLensApplication) { |
| DexType type = clazz.getType(); |
| DexType renamed = lens.lookupType(type); |
| if (renamed.isIntType()) { |
| continue; |
| } |
| if (renamed != type) { |
| if (definitionFor(type) == null && definitionFor(renamed) != null) { |
| continue; |
| } |
| assert definitionFor(type).type == renamed || definitionFor(renamed) != null |
| : "The lens and app is inconsistent"; |
| } |
| } |
| return true; |
| } |
| |
| // Debugging helper to compute the code-object owner map. |
| public Map<Code, DexEncodedMethod> computeCodeObjectOwnersForDebugging() { |
| // Call the verification method without assert to ensure owners are computed. |
| verifyCodeObjectsOwners(); |
| return codeOwners; |
| } |
| |
| // Debugging helper to find the method a code object belongs to. |
| public DexEncodedMethod getCodeOwnerForDebugging(Code code) { |
| return computeCodeObjectOwnersForDebugging().get(code); |
| } |
| |
| public boolean verifyCodeObjectsOwners() { |
| codeOwners.clear(); |
| for (DexProgramClass clazz : programClasses) { |
| for (DexEncodedMethod method : |
| clazz.methods(DexEncodedMethod::isNonAbstractNonNativeMethod)) { |
| Code code = method.getCode(); |
| assert code != null; |
| // If code is (lazy) CF code, then use the CF code object rather than the lazy wrapper. |
| if (code.isCfCode()) { |
| code = code.asCfCode(); |
| } else if (code.isSharedCodeObject()) { |
| continue; |
| } |
| DexEncodedMethod otherMethod = codeOwners.put(code, method); |
| assert otherMethod == null; |
| } |
| } |
| return true; |
| } |
| |
| public static class Builder extends DexApplication.Builder<Builder> { |
| |
| private ImmutableCollection<DexClasspathClass> classpathClasses; |
| private Map<DexType, DexLibraryClass> libraryClasses; |
| |
| private final List<DexClasspathClass> pendingClasspathClasses = new ArrayList<>(); |
| private final Set<DexType> pendingClasspathRemovalIfPresent = Sets.newIdentityHashSet(); |
| |
| Builder(LazyLoadedDexApplication application) { |
| super(application); |
| // As a side-effect, this will force-load all classes. |
| AllClasses allClasses = application.loadAllClasses(); |
| classpathClasses = allClasses.getClasspathClasses().values(); |
| libraryClasses = allClasses.getLibraryClasses(); |
| replaceProgramClasses(allClasses.getProgramClasses().values()); |
| } |
| |
| private Builder(DirectMappedDexApplication application) { |
| super(application); |
| classpathClasses = application.classpathClasses; |
| libraryClasses = application.libraryClasses; |
| } |
| |
| @Override |
| public boolean isDirect() { |
| return true; |
| } |
| |
| @Override |
| public Builder asDirect() { |
| return this; |
| } |
| |
| @Override |
| public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) { |
| addProgramClass(clazz); |
| pendingClasspathRemovalIfPresent.add(clazz.type); |
| // When java.lang.Record is added remove the library class, as this program class will |
| // eventually be renamed to com.android.tools.r8.RecordTag so there should not be a |
| // multi-resolution result when looking up java.lang.Record. |
| if (clazz.type.isIdenticalTo(options.dexItemFactory().recordType) |
| && libraryClasses.containsKey(clazz.type)) { |
| ensureMutableLibraryClassesMap(); |
| libraryClasses.remove(clazz.type); |
| } |
| } |
| |
| private void ensureMutableLibraryClassesMap() { |
| if (!(libraryClasses instanceof IdentityHashMap)) { |
| libraryClasses = new IdentityHashMap<>(libraryClasses); |
| } |
| } |
| |
| @Override |
| Builder self() { |
| return this; |
| } |
| |
| public Builder addClasspathClass(DexClasspathClass clazz) { |
| pendingClasspathClasses.add(clazz); |
| return self(); |
| } |
| |
| private void commitPendingClasspathClasses() { |
| if (!pendingClasspathClasses.isEmpty()) { |
| classpathClasses = |
| ImmutableList.<DexClasspathClass>builder() |
| .addAll(classpathClasses) |
| .addAll(pendingClasspathClasses) |
| .build(); |
| pendingClasspathClasses.clear(); |
| } |
| } |
| |
| public Builder replaceClasspathClasses(Collection<DexClasspathClass> newClasspathClasses) { |
| return replaceClasspathClasses(ImmutableList.copyOf(newClasspathClasses)); |
| } |
| |
| public Builder replaceClasspathClasses(ImmutableList<DexClasspathClass> newClasspathClasses) { |
| assert newClasspathClasses != null; |
| classpathClasses = newClasspathClasses; |
| pendingClasspathClasses.clear(); |
| return self(); |
| } |
| |
| public Builder removeClasspathClasses(Predicate<DexClasspathClass> predicate) { |
| ImmutableList.Builder<DexClasspathClass> newClasspathClasses = ImmutableList.builder(); |
| for (DexClasspathClass clazz : classpathClasses) { |
| if (!predicate.test(clazz)) { |
| newClasspathClasses.add(clazz); |
| } |
| } |
| for (DexClasspathClass clazz : pendingClasspathClasses) { |
| if (!predicate.test(clazz)) { |
| newClasspathClasses.add(clazz); |
| } |
| } |
| return replaceClasspathClasses(newClasspathClasses.build()); |
| } |
| |
| public Builder replaceLibraryClasses(Collection<DexLibraryClass> libraryClasses) { |
| ImmutableMap.Builder<DexType, DexLibraryClass> builder = ImmutableMap.builder(); |
| libraryClasses.forEach(clazz -> builder.put(clazz.type, clazz)); |
| this.libraryClasses = builder.build(); |
| return self(); |
| } |
| |
| @Override |
| public DirectMappedDexApplication build() { |
| // Rebuild the map. This will fail if keys are not unique. |
| // TODO(zerny): Consider not rebuilding the map if no program classes are added. |
| commitPendingClasspathClasses(); |
| Map<DexType, ProgramOrClasspathClass> programAndClasspathClasses = |
| new IdentityHashMap<>(getProgramClasses().size() + classpathClasses.size()); |
| // Note: writing classes in reverse priority order, so a duplicate will be correctly ordered. |
| // There should not be duplicates between program and classpath and that is asserted in the |
| // addAll subroutine. |
| ImmutableCollection<DexClasspathClass> newClasspathClasses = classpathClasses; |
| if (addAll(programAndClasspathClasses, classpathClasses)) { |
| ImmutableList.Builder<DexClasspathClass> builder = ImmutableList.builder(); |
| for (DexClasspathClass classpathClass : classpathClasses) { |
| if (!pendingClasspathRemovalIfPresent.contains(classpathClass.getType())) { |
| builder.add(classpathClass); |
| } |
| } |
| newClasspathClasses = builder.build(); |
| } |
| addAll(programAndClasspathClasses, getProgramClasses()); |
| return new DirectMappedDexApplication( |
| proguardMap, |
| flags, |
| ImmutableMap.copyOf(programAndClasspathClasses), |
| getLibraryClassesAsImmutableMap(), |
| ImmutableList.copyOf(getProgramClasses()), |
| newClasspathClasses, |
| ImmutableList.copyOf(dataResourceProviders), |
| options, |
| timing); |
| } |
| |
| private <T extends ProgramOrClasspathClass> boolean addAll( |
| Map<DexType, ProgramOrClasspathClass> allClasses, Iterable<T> toAdd) { |
| boolean seenRemoved = false; |
| for (T clazz : toAdd) { |
| if (clazz.isProgramClass() || !pendingClasspathRemovalIfPresent.contains(clazz.getType())) { |
| ProgramOrClasspathClass old = allClasses.put(clazz.getType(), clazz); |
| assert old == null : "Class " + old.getType().toString() + " was already present."; |
| } else { |
| seenRemoved = true; |
| } |
| } |
| return seenRemoved; |
| } |
| |
| private ImmutableMap<DexType, DexLibraryClass> getLibraryClassesAsImmutableMap() { |
| if (libraryClasses instanceof ImmutableMap) { |
| return (ImmutableMap<DexType, DexLibraryClass>) libraryClasses; |
| } else { |
| return ImmutableMap.copyOf(libraryClasses); |
| } |
| } |
| } |
| } |