| // 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. |
| package com.android.tools.r8.shaking; |
| |
| import com.android.tools.r8.dex.IndexedItemCollection; |
| import com.android.tools.r8.errors.CompilationError; |
| import com.android.tools.r8.graph.AppInfoWithSubtyping; |
| import com.android.tools.r8.graph.DexAnnotation; |
| import com.android.tools.r8.graph.DexAnnotationSet; |
| import com.android.tools.r8.graph.DexApplication; |
| import com.android.tools.r8.graph.DexCallSite; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexMethodHandle; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexProto; |
| import com.android.tools.r8.graph.DexString; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.UseRegistry; |
| import com.google.common.collect.Maps; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Calculate the list of classes required in the main dex to allow legacy multidex loading. |
| * Classes required in the main dex are: |
| * <li> The classes with code executed before secondary dex files are installed. |
| * <li> The "direct dependencies" of those classes, ie the classes required by dexopt. |
| * <li> Annotation classes with a possible enum value and all classes annotated by them. |
| */ |
| public class MainDexListBuilder { |
| private final Set<DexType> baseClasses; |
| private final AppInfoWithSubtyping appInfo; |
| private final Set<DexType> mainDexTypes = new HashSet<>(); |
| private final DirectReferencesCollector codeDirectReferenceCollector = |
| new DirectReferencesCollector(); |
| private final AnnotationDirectReferenceCollector annotationDirectReferenceCollector = |
| new AnnotationDirectReferenceCollector(); |
| private final Map<DexType, Boolean> annotationTypeContainEnum; |
| private final DexApplication dexApplication; |
| |
| /** |
| * @param baseClasses Classes which code may be executed before secondary dex files loading. |
| * @param application the dex appplication. |
| */ |
| public MainDexListBuilder(Set<DexType> baseClasses, DexApplication application) { |
| this.dexApplication = application; |
| this.appInfo = new AppInfoWithSubtyping(dexApplication); |
| this.baseClasses = |
| baseClasses.stream().filter(this::isProgramClass).collect(Collectors.toSet()); |
| DexClass enumType = appInfo.definitionFor(appInfo.dexItemFactory.enumType); |
| if (enumType == null) { |
| throw new CompilationError("Tracing for legacy multi dex is not possible without all" |
| + " classpath libraries (java.lang.Enum is missing)"); |
| } |
| DexClass annotationType = appInfo.definitionFor(appInfo.dexItemFactory.annotationType); |
| if (annotationType == null) { |
| throw new CompilationError("Tracing for legacy multi dex is not possible without all" |
| + " classpath libraries (java.lang.annotation.Annotation is missing)"); |
| } |
| annotationTypeContainEnum = |
| Maps.newHashMapWithExpectedSize( |
| appInfo.subtypes(appInfo.dexItemFactory.annotationType).size()); |
| } |
| |
| public Set<DexType> run() { |
| traceMainDexDirectDependencies(); |
| traceRuntimeAnnotationsWithEnumForMainDex(); |
| return mainDexTypes.stream().filter(this::isProgramClass).collect(Collectors.toSet()); |
| } |
| |
| private void traceRuntimeAnnotationsWithEnumForMainDex() { |
| for (DexProgramClass clazz : dexApplication.classes()) { |
| DexType dexType = clazz.type; |
| if (mainDexTypes.contains(dexType)) { |
| continue; |
| } |
| if (isAnnotation(dexType) && isAnnotationWithEnum(dexType)) { |
| addMainDexType(dexType); |
| continue; |
| } |
| clazz.forEachAnnotation(annotation -> { |
| if (!mainDexTypes.contains(dexType) |
| && annotation.visibility == DexAnnotation.VISIBILITY_RUNTIME |
| && isAnnotationWithEnum(annotation.annotation.type)) { |
| addMainDexType(dexType); |
| } |
| }); |
| } |
| } |
| |
| private boolean isAnnotationWithEnum(DexType dexType) { |
| Boolean value = annotationTypeContainEnum.get(dexType); |
| if (value == null) { |
| DexClass clazz = appInfo.definitionFor(dexType); |
| if (clazz == null) { |
| // Information is missing lets be conservative. |
| value = Boolean.TRUE; |
| } else { |
| value = Boolean.FALSE; |
| // Browse annotation values types in search for enum. |
| // Each annotation value is represented by a virtual method. |
| for (DexEncodedMethod method : clazz.virtualMethods()) { |
| DexProto proto = method.method.proto; |
| if (proto.parameters.isEmpty()) { |
| DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory); |
| if (isEnum(valueType)) { |
| value = Boolean.TRUE; |
| break; |
| } else if (isAnnotation(valueType) && isAnnotationWithEnum(valueType)) { |
| value = Boolean.TRUE; |
| break; |
| } |
| } |
| } |
| } |
| annotationTypeContainEnum.put(dexType, value); |
| } |
| return value.booleanValue(); |
| } |
| |
| private boolean isEnum(DexType valueType) { |
| return valueType.isSubtypeOf(appInfo.dexItemFactory.enumType, appInfo); |
| } |
| |
| private boolean isAnnotation(DexType valueType) { |
| return valueType.isSubtypeOf(appInfo.dexItemFactory.annotationType, appInfo); |
| } |
| |
| private boolean isProgramClass(DexType dexType) { |
| DexClass clazz = appInfo.definitionFor(dexType); |
| return clazz != null && clazz.isProgramClass(); |
| } |
| |
| private void traceMainDexDirectDependencies() { |
| for (DexType type : baseClasses) { |
| DexClass clazz = appInfo.definitionFor(type); |
| if (clazz == null) { |
| // Happens for library classes. |
| continue; |
| } |
| addMainDexType(type); |
| // Super and interfaces are live, no need to add them. |
| traceAnnotationsDirectDendencies(clazz.annotations); |
| clazz.forEachField(field -> addMainDexType(field.field.type)); |
| clazz.forEachMethod(method -> { |
| traceMethodDirectDependencies(method.method); |
| method.registerCodeReferences(codeDirectReferenceCollector); |
| }); |
| } |
| } |
| |
| private void traceAnnotationsDirectDendencies(DexAnnotationSet annotations) { |
| annotations.collectIndexedItems(annotationDirectReferenceCollector); |
| } |
| |
| private void traceMethodDirectDependencies(DexMethod method) { |
| DexProto proto = method.proto; |
| addMainDexType(proto.returnType); |
| for (DexType parameterType : proto.parameters.values) { |
| addMainDexType(parameterType); |
| } |
| } |
| |
| private void addMainDexType(DexType type) { |
| // Consider only component type of arrays |
| type = type.toBaseType(appInfo.dexItemFactory); |
| |
| if (!type.isClassType()) { |
| return; |
| } |
| |
| DexClass clazz = appInfo.definitionFor(type); |
| if (clazz == null) { |
| // Happens for library classes. |
| return; |
| } |
| addMainDexType(clazz); |
| } |
| |
| private void addMainDexType(DexClass dexClass) { |
| DexType type = dexClass.type; |
| if (mainDexTypes.add(type)) { |
| if (dexClass.superType != null) { |
| addMainDexType(dexClass.superType); |
| } |
| for (DexType interfaze : dexClass.interfaces.values) { |
| addMainDexType(interfaze); |
| } |
| } |
| } |
| |
| private class DirectReferencesCollector extends UseRegistry { |
| |
| |
| private DirectReferencesCollector() { |
| } |
| |
| @Override |
| public boolean registerInvokeVirtual(DexMethod method) { |
| return registerInvoke(method); |
| } |
| |
| @Override |
| public boolean registerInvokeDirect(DexMethod method) { |
| return registerInvoke(method); |
| } |
| |
| @Override |
| public boolean registerInvokeStatic(DexMethod method) { |
| return registerInvoke(method); |
| } |
| |
| @Override |
| public boolean registerInvokeInterface(DexMethod method) { |
| return registerInvoke(method); |
| } |
| |
| @Override |
| public boolean registerInvokeSuper(DexMethod method) { |
| return registerInvoke(method); |
| } |
| |
| protected boolean registerInvoke(DexMethod method) { |
| addMainDexType(method.getHolder()); |
| traceMethodDirectDependencies(method); |
| return true; |
| } |
| |
| @Override |
| public boolean registerInstanceFieldWrite(DexField field) { |
| return registerFieldAccess(field); |
| } |
| |
| @Override |
| public boolean registerInstanceFieldRead(DexField field) { |
| return registerFieldAccess(field); |
| } |
| |
| @Override |
| public boolean registerStaticFieldRead(DexField field) { |
| return registerFieldAccess(field); |
| } |
| |
| @Override |
| public boolean registerStaticFieldWrite(DexField field) { |
| return registerFieldAccess(field); |
| } |
| |
| protected boolean registerFieldAccess(DexField field) { |
| addMainDexType(field.getHolder()); |
| addMainDexType(field.type); |
| return true; |
| } |
| |
| @Override |
| public boolean registerNewInstance(DexType type) { |
| addMainDexType(type); |
| return true; |
| } |
| |
| @Override |
| public boolean registerTypeReference(DexType type) { |
| addMainDexType(type); |
| return true; |
| } |
| } |
| |
| private class AnnotationDirectReferenceCollector implements IndexedItemCollection { |
| |
| @Override |
| public boolean addClass(DexProgramClass dexProgramClass) { |
| addMainDexType(dexProgramClass.type); |
| return false; |
| } |
| |
| @Override |
| public boolean addField(DexField field) { |
| addMainDexType(field.getHolder()); |
| addMainDexType(field.type); |
| return false; |
| } |
| |
| @Override |
| public boolean addMethod(DexMethod method) { |
| addMainDexType(method.getHolder()); |
| addProto(method.proto); |
| return false; |
| } |
| |
| @Override |
| public boolean addString(DexString string) { |
| return false; |
| } |
| |
| @Override |
| public boolean addProto(DexProto proto) { |
| addMainDexType(proto.returnType); |
| Collections.addAll(mainDexTypes, proto.parameters.values); |
| return false; |
| } |
| |
| @Override |
| public boolean addType(DexType type) { |
| addMainDexType(type); |
| return false; |
| } |
| |
| @Override |
| public boolean addCallSite(DexCallSite callSite) { |
| throw new AssertionError("CallSite are not supported when tracing for legacy multi dex"); |
| } |
| |
| @Override |
| public boolean addMethodHandle(DexMethodHandle methodHandle) { |
| throw new AssertionError( |
| "DexMethodHandle are not supported when tracing for legacy multi dex"); |
| } |
| } |
| } |