| // 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.errors.CompilationError; |
| import com.android.tools.r8.graph.AppInfo; |
| import com.android.tools.r8.graph.AppInfoWithClassHierarchy; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexAnnotation; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexProto; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.utils.SetUtils; |
| import java.util.IdentityHashMap; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * 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> roots; |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| private final Map<DexType, Boolean> annotationTypeContainEnum; |
| private final MainDexTracingResult.Builder mainDexClassesBuilder; |
| |
| public static void checkForAssumedLibraryTypes(AppInfo appInfo) { |
| 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)"); |
| } |
| } |
| |
| /** |
| * @param roots Classes which code may be executed before secondary dex files loading. |
| * @param appView the dex appplication. |
| */ |
| public MainDexListBuilder( |
| Set<DexProgramClass> roots, AppView<? extends AppInfoWithClassHierarchy> appView) { |
| this.appView = appView; |
| // Only consider program classes for the root set. |
| this.roots = SetUtils.mapIdentityHashSet(roots, DexProgramClass::getType); |
| mainDexClassesBuilder = MainDexTracingResult.builder(appView.appInfo()).addRoots(this.roots); |
| annotationTypeContainEnum = new IdentityHashMap<>(); |
| } |
| |
| private AppInfoWithClassHierarchy appInfo() { |
| return appView.appInfo(); |
| } |
| |
| public MainDexTracingResult run() { |
| traceMainDexDirectDependencies(); |
| traceRuntimeAnnotationsWithEnumForMainDex(); |
| return mainDexClassesBuilder.build(); |
| } |
| |
| private void traceRuntimeAnnotationsWithEnumForMainDex() { |
| for (DexProgramClass clazz : appInfo().classes()) { |
| DexType dexType = clazz.type; |
| if (mainDexClassesBuilder.contains(dexType)) { |
| continue; |
| } |
| if (isAnnotation(dexType) && isAnnotationWithEnum(dexType)) { |
| addAnnotationsWithEnum(clazz); |
| continue; |
| } |
| // Classes with annotations must be in the same dex file as the annotation. As all |
| // annotations with enums goes into the main dex, move annotated classes there as well. |
| clazz.forEachAnnotation( |
| annotation -> { |
| if (!mainDexClassesBuilder.contains(dexType) |
| && annotation.visibility == DexAnnotation.VISIBILITY_RUNTIME |
| && isAnnotationWithEnum(annotation.annotation.type)) { |
| addClassAnnotatedWithAnnotationWithEnum(dexType); |
| } |
| }); |
| } |
| } |
| |
| private boolean isAnnotationWithEnum(DexType dexType) { |
| Boolean value = annotationTypeContainEnum.get(dexType); |
| if (value == null) { |
| DexClass clazz = appView.definitionFor(dexType); |
| if (clazz == null) { |
| // Information is missing lets be conservative. |
| value = true; |
| } else { |
| value = 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(appView.dexItemFactory()); |
| if (valueType.isClassType()) { |
| if (isEnum(valueType)) { |
| value = true; |
| break; |
| } else if (isAnnotation(valueType) && isAnnotationWithEnum(valueType)) { |
| value = true; |
| break; |
| } |
| } |
| } |
| } |
| } |
| annotationTypeContainEnum.put(dexType, value); |
| } |
| return value; |
| } |
| |
| private boolean isEnum(DexType valueType) { |
| return valueType.isClassType() |
| && appInfo().isSubtype(valueType, appView.dexItemFactory().enumType); |
| } |
| |
| private boolean isAnnotation(DexType valueType) { |
| return appInfo().isSubtype(valueType, appView.dexItemFactory().annotationType); |
| } |
| |
| private void traceMainDexDirectDependencies() { |
| new MainDexDirectReferenceTracer(appInfo(), this::addDirectDependency).run(roots); |
| } |
| |
| private void addAnnotationsWithEnum(DexProgramClass clazz) { |
| // Add the annotation class as a direct dependency. |
| addDirectDependency(clazz); |
| // Add enum classes used for values as direct dependencies. |
| for (DexEncodedMethod method : clazz.virtualMethods()) { |
| DexProto proto = method.method.proto; |
| if (proto.parameters.isEmpty()) { |
| DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory()); |
| if (isEnum(valueType)) { |
| addDirectDependency(valueType); |
| } |
| } |
| } |
| } |
| |
| private void addClassAnnotatedWithAnnotationWithEnum(DexType type) { |
| // Just add classes annotated with annotations with enum ad direct dependencies. |
| addDirectDependency(type); |
| } |
| |
| private void addDirectDependency(DexType type) { |
| // Consider only component type of arrays |
| type = type.toBaseType(appView.dexItemFactory()); |
| |
| if (!type.isClassType() || mainDexClassesBuilder.contains(type)) { |
| return; |
| } |
| |
| DexClass clazz = appView.definitionFor(type); |
| // No library classes in main-dex. |
| if (clazz == null || clazz.isNotProgramClass()) { |
| return; |
| } |
| addDirectDependency(clazz.asProgramClass()); |
| } |
| |
| private void addDirectDependency(DexProgramClass dexClass) { |
| DexType type = dexClass.type; |
| assert !mainDexClassesBuilder.contains(type); |
| mainDexClassesBuilder.addDependency(type); |
| if (dexClass.superType != null) { |
| addDirectDependency(dexClass.superType); |
| } |
| for (DexType interfaze : dexClass.interfaces.values) { |
| addDirectDependency(interfaze); |
| } |
| } |
| } |