blob: 11835906bc76858735ec6aa801b08854d2bf2951 [file] [log] [blame]
// Copyright (c) 2018, 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.dex;
import com.android.tools.r8.dex.VirtualFile.VirtualFileCycler;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/**
* Partition classes among dex files to limit LinearAlloc usage during DexOpt.
*
* This is achieved by ensuring that each class has all it's hierarchy either in the bootclasspath
* or in the same secondary dex. This is preventing linking errors for those classes. Then if it's
* not possible to respect this constraint for some classes, instead ensure that those classes are
* put in a different secondary dex than all their link dependents (i.e. subclasses,
* implementations or sub interfaces). Those classes will failed to link during DexOpt but they will
* be loaded only once, this ensures that DexOpt will not use any extra LinearAlloc space for those
* classes in linking error.
*/
public class InheritanceClassInDexDistributor {
private static final Comparator<DexProgramClass> DEX_PROGRAM_CLASS_COMPARATOR =
(a, b) -> a.type.descriptor.compareTo(b.type.descriptor);
private static final int DEX_FULL_ENOUGH_THRESHOLD = VirtualFile.MAX_ENTRIES - 100;
private final ExecutorService executorService;
/**
* Group of classes.
*/
private class ClassGroup implements Comparable<ClassGroup> {
public final Set<DexProgramClass> members;
public int numberOfFieldIds = -1;
public int numberOfMethodIds = -1;
public boolean dependsOnMainDexClasses = false;
public ClassGroup() {
members = new HashSet<>();
}
public ClassGroup(Set<DexProgramClass> members) {
this.members = members;
updateNumbersOfIds();
}
public void updateNumbersOfIds() {
// Use a temporary VirtualFile to evaluate the number of ids in the group.
VirtualFile virtualFile = new VirtualFile(0, appView, graphLens, initClassLens, namingLens);
// Note: sort not needed.
for (DexProgramClass clazz : members) {
virtualFile.addClass(clazz);
}
numberOfFieldIds = virtualFile.getNumberOfFields();
numberOfMethodIds = virtualFile.getNumberOfMethods();
}
public boolean canFitInOneDex() {
return numberOfFieldIds < VirtualFile.MAX_ENTRIES
&& numberOfMethodIds < VirtualFile.MAX_ENTRIES;
}
// This is used for sorting. Compared groups must be disjoint.
@Override
public int compareTo(ClassGroup other) {
assert !(
members.isEmpty()
|| other.members.isEmpty()
|| numberOfFieldIds == -1 || numberOfMethodIds == -1);
if (this == other) {
return 0;
}
if (numberOfMethodIds != other.numberOfMethodIds) {
return numberOfMethodIds - other.numberOfMethodIds;
}
if (numberOfFieldIds != other.numberOfFieldIds) {
return numberOfFieldIds - other.numberOfFieldIds;
}
if (members.size() != other.members.size()) {
return members.size() - other.members.size();
}
// We can end up here frequently with one element groups, but it seems very unlikely if the
// groups grow significantly bigger.
int result = DEX_PROGRAM_CLASS_COMPARATOR.compare(
getSortedCopy(members).iterator().next(),
getSortedCopy(other.members).iterator().next());
assert result != 0;
return result;
}
}
/**
* For {@link ClassGroup} with a dependency to the main dex classes. Allow to split the
* members in 3 categories:
* - Category 1: members which could go to a secondary dex altogether without facing linking
* error.
* - Category 2: members which could go to the main dex without needing category 1 members to link
* - Category 3: others members of the group, those which will fail to link unless the whole group
* goes into the main dex.
*/
private class CategorizedInheritanceGroupWithMainDexDependency {
/** Category 1. */
final Set<DexProgramClass> mainDexIndependents = new HashSet<>();
/** Category 2. Main dex dependents independents from mainDexIndependents elements. */
final Set<DexProgramClass> independentsFromMainDexIndependents = new HashSet<>();
/** Category 3. Main dex dependents also depending on some mainDexIndependents elements. */
final Set<DexProgramClass> dependentsOfMainDexIndependents = new HashSet<>();
CategorizedInheritanceGroupWithMainDexDependency(ClassGroup group) {
int totalClassNumber = group.members.size();
/**
* Category 2 + category 3 elements. Used during construction only.
*/
Set<DexProgramClass> mainDexDependents = new HashSet<>();
// split group members between mainDexIndependents and mainDexDependents
// Note: sort not needed.
for (DexProgramClass candidate : group.members) {
isDependingOnMainDexClass(mainDexDependents, candidate);
}
// split mainDexDependents members between independentsFromMainDexIndependents and
// dependentsOfMainDexIndependents
for (DexProgramClass candidate : mainDexDependents) {
isDependingOnMainDexIndependents(candidate);
}
assert totalClassNumber ==
mainDexIndependents.size()
+ dependentsOfMainDexIndependents.size()
+ independentsFromMainDexIndependents.size();
}
private boolean isDependingOnMainDexClass(
Set<DexProgramClass> mainDexDependents, DexProgramClass clazz) {
if (clazz == null) {
return false;
}
// Think: build on one Map<dexProgramClass, Boolean> and split in a second step.
if (mainDexIndependents.contains(clazz)) {
return false;
}
if (mainDexDependents.contains(clazz)) {
return true;
}
if (mainDex.classes().contains(clazz)) {
return true;
}
boolean isDependent = false;
if (isDependingOnMainDexClass(
mainDexDependents, appView.programDefinitionFor(clazz.superType, clazz))) {
isDependent = true;
} else {
for (DexType interfaze : clazz.interfaces.values) {
if (isDependingOnMainDexClass(
mainDexDependents, appView.programDefinitionFor(interfaze, clazz))) {
isDependent = true;
break;
}
}
}
if (isDependent) {
mainDexDependents.add(clazz);
} else {
mainDexIndependents.add(clazz);
}
return isDependent;
}
private boolean isDependingOnMainDexIndependents(DexProgramClass clazz) {
if (clazz == null) {
return false;
}
// Think: build on one Map<dexProgramClass, Boolean> and split in a second step.
if (independentsFromMainDexIndependents.contains(clazz)) {
return false;
}
if (dependentsOfMainDexIndependents.contains(clazz)) {
return true;
}
if (mainDex.classes().contains(clazz)) {
return false;
}
if (mainDexIndependents.contains(clazz)) {
return true;
}
boolean isDependent = false;
if (isDependingOnMainDexIndependents(appView.programDefinitionFor(clazz.superType, clazz))) {
isDependent = true;
} else {
for (DexType interfaze : clazz.interfaces.values) {
if (isDependingOnMainDexIndependents(appView.programDefinitionFor(interfaze, clazz))) {
isDependent = true;
break;
}
}
}
if (isDependent) {
dependentsOfMainDexIndependents.add(clazz);
} else {
independentsFromMainDexIndependents.add(clazz);
}
return isDependent;
}
}
/**
* Collect direct inheritance relation between a set of {@link DexProgramClass}.
* This is not a general purpose tool: it's ignoring any inheritance relation with classes outside
* of the provided set.
*/
private static class DirectSubClassesInfo {
/** Class or interface to direct subclasses or direct sub interfaces and implementing classes */
private final Map<DexProgramClass, Collection<DexProgramClass>> directSubClasses;
private final Set<DexProgramClass> classes;
DirectSubClassesInfo(AppView<?> appView, Set<DexProgramClass> classes) {
Map<DexProgramClass, Collection<DexProgramClass>> directSubClasses = new HashMap<>();
for (DexProgramClass clazz : classes) {
addDirectSubClass(appView, classes, directSubClasses, clazz.superType, clazz);
for (DexType interfaze : clazz.interfaces.values) {
addDirectSubClass(appView, classes, directSubClasses, interfaze, clazz);
}
}
this.classes = classes;
this.directSubClasses = directSubClasses;
}
Collection<DexProgramClass> getDirectSubClasses(DexProgramClass clazz) {
assert classes.contains(clazz);
return directSubClasses.getOrDefault(clazz, Collections.emptyList());
}
private static void addDirectSubClass(
AppView<?> appView,
Set<DexProgramClass> classes,
Map<DexProgramClass, Collection<DexProgramClass>> directSubClasses,
DexType superType,
DexProgramClass clazz) {
DexProgramClass zuper = appView.programDefinitionFor(superType, clazz);
// Don't bother collecting subclasses info that we won't use.
if (zuper != null && classes.contains(zuper)) {
Collection<DexProgramClass> subClasses =
directSubClasses.computeIfAbsent(zuper, unused -> new ArrayList<>());
subClasses.add(clazz);
}
}
}
private final VirtualFile mainDex;
private final List<VirtualFile> dexes;
private final BitSet fullDex = new BitSet();
private final Set<DexProgramClass> classes;
private final AppView<?> appView;
private int dexIndexOffset;
private final GraphLens graphLens;
private final InitClassLens initClassLens;
private final NamingLens namingLens;
private final DirectSubClassesInfo directSubClasses;
public InheritanceClassInDexDistributor(
VirtualFile mainDex,
List<VirtualFile> dexes,
Set<DexProgramClass> classes,
int dexIndexOffset,
GraphLens graphLens,
InitClassLens initClassLens,
NamingLens namingLens,
AppView<?> appView,
ExecutorService executorService) {
this.mainDex = mainDex;
this.dexes = dexes;
this.classes = classes;
this.dexIndexOffset = dexIndexOffset;
this.graphLens = graphLens;
this.initClassLens = initClassLens;
this.namingLens = namingLens;
this.appView = appView;
this.executorService = executorService;
directSubClasses = new DirectSubClassesInfo(appView, classes);
}
public void distribute() {
List<ClassGroup> remainingInheritanceGroups = collectInheritanceGroups();
// Sort to ensure reproducible allocation
remainingInheritanceGroups.sort(null);
// Starts with big groups since they are more likely to cause problem.
Collections.reverse(remainingInheritanceGroups);
// Allocate member of groups depending on
// the main dex members
for (Iterator<ClassGroup> iter = remainingInheritanceGroups.iterator(); iter.hasNext();) {
ClassGroup group = iter.next();
if (group.dependsOnMainDexClasses) {
iter.remove();
// Try to assign the whole group to the main dex
if (group.canFitInOneDex()
&& !isDexFull(mainDex)
&& assignAll(mainDex, group.members)) {
// It fitted, so work done
continue;
}
// We can't put the whole group in the main dex, let's split the group to try to make the
// best of it.
CategorizedInheritanceGroupWithMainDexDependency groupSplit =
new CategorizedInheritanceGroupWithMainDexDependency(group);
// Attempt to assign the "main dex dependent classes independents from mainDexIndependents"
// to the main dex where they can link.
Collection<DexProgramClass> classesMissingMainDex =
assignFromRoot(mainDex, groupSplit.independentsFromMainDexIndependents);
// Assign mainDexIndependents classes, those can link as long as the group fit into one dex
ClassGroup mainDexIndependentGroup =
new ClassGroup(groupSplit.mainDexIndependents);
Collection<VirtualFile> mainDexInpendentsDexes =
assignGroup(mainDexIndependentGroup, Collections.singletonList(mainDex));
Set<DexProgramClass> classesWithLinkingError =
new HashSet<>(groupSplit.dependentsOfMainDexIndependents);
classesWithLinkingError.addAll(classesMissingMainDex);
assignClassesWithLinkingError(classesWithLinkingError, mainDexInpendentsDexes);
}
}
// Allocate member of groups independents from the main dex members
for (ClassGroup group : remainingInheritanceGroups) {
if (!group.dependsOnMainDexClasses) {
assignGroup(group, Collections.emptyList());
}
}
}
private static int getTotalClassNumber(List<ClassGroup> groups) {
int groupClassNumber = 0;
for (ClassGroup group : groups) {
groupClassNumber += group.members.size();
}
return groupClassNumber;
}
private Collection<VirtualFile> assignGroup(ClassGroup group, List<VirtualFile> exclude) {
VirtualFileCycler cycler =
new VirtualFileCycler(dexes, appView, graphLens, initClassLens, namingLens, dexIndexOffset);
if (group.members.isEmpty()) {
return Collections.emptyList();
} else if (group.canFitInOneDex()) {
VirtualFile currentDex;
while (true) {
currentDex = cycler.nextOrCreate(dex -> !exclude.contains(dex) && !isDexFull(dex));
if (assignAll(currentDex, group.members)) {
break;
}
}
return Collections.singletonList(currentDex);
} else {
// put as much as possible of the group in an empty dex.
// About the existing dexes: only case when there can be an empty one is when this.dexes
// contains only one empty dex created as initial dex of a minimal main dex run. So no need to
// run through all the existing dexes to find for a possible empty candidate, if the next one
// is not empty, no other existing dex is. This means the exclude check is redundant but
// its cost is negligible so no need to go wild on optimization and let's be safe.
VirtualFile dexForLinkingClasses =
cycler.nextOrCreate(dex -> dex.isEmpty() && !exclude.contains(dex) && !isDexFull(dex));
Set<DexProgramClass> remaining = assignFromRoot(dexForLinkingClasses, group.members);
// Assign remainingclasses so that they are never in the same dex as their subclasses.
// They will fail to link during DexOpt but they will be loaded only once.
// For now use a "leaf layer by dex" algorithm to ensure the constrainst.
Collection<VirtualFile> newExclude = new HashSet<>(exclude);
newExclude.add(dexForLinkingClasses);
Collection<VirtualFile> usedDex = assignClassesWithLinkingError(remaining, newExclude);
usedDex.add(dexForLinkingClasses);
return usedDex;
}
}
/**
* Assign classes so that they are never in the same dex as their subclasses. They will fail to
* link during DexOpt but they will be loaded only once.
*
* @param classes set of classes to assign, the set will be destroyed during assignment.
*/
private Collection<VirtualFile> assignClassesWithLinkingError(
Set<DexProgramClass> classes, Collection<VirtualFile> exclude) {
List<ClassGroup> layers = collectNoDirectInheritanceGroups(classes);
Collections.sort(layers);
Collection<VirtualFile> usedDex = new ArrayList<>();
VirtualFileCycler cycler =
new VirtualFileCycler(dexes, appView, graphLens, initClassLens, namingLens, dexIndexOffset);
// Don't modify exclude. Think about modifying the input collection considering this
// is private API.
Set<VirtualFile> currentExclude = new HashSet<>(exclude);
// Don't put class expected to fail linking in the main dex, save main dex space for classes
// that may benefit to be in the main dex.
currentExclude.add(mainDex);
for (ClassGroup group : layers) {
cycler.reset();
VirtualFile dexForLayer =
cycler.nextOrCreate(dex -> !currentExclude.contains(dex) && !isDexFull(dex));
currentExclude.add(dexForLayer);
usedDex.add(dexForLayer);
for (DexProgramClass dexProgramClass : getSortedCopy(group.members)) {
while (true) {
dexForLayer.addClass(dexProgramClass);
if (dexForLayer.isFull()) {
dexForLayer.abortTransaction();
if (dexForLayer.isEmpty()) {
// The class is too big to fit in one dex
throw new CompilationError("Class '" + dexProgramClass.toSourceString()
+ "' from " + dexProgramClass.getOrigin().toString()
+ " is too big to fit in a dex.");
}
if (dexForLayer.isFull(DEX_FULL_ENOUGH_THRESHOLD)) {
markDexFull(dexForLayer);
}
// Current dex is too full, continue to next dex.
dexForLayer =
cycler.nextOrCreate(dex -> !currentExclude.contains(dex) && !isDexFull(dex));
currentExclude.add(dexForLayer);
usedDex.add(dexForLayer);
} else {
dexForLayer.commitTransaction();
break;
}
}
}
}
return usedDex;
}
/**
* Distribute given classes in groups where each group never contains 2 classes with a direct
* inheritance relation. For example:<br>
* I3 --> I1<br>
* I4 --> I2<br>
* I5 --> I1, I2<br>
* One valid result can be {I3, I4, I5} and {I1,I2}. This method attempts to return a small number
* of groups but does not guaranty to produce the minimal possible solution.
*/
private List<ClassGroup> collectNoDirectInheritanceGroups(Set<DexProgramClass> classes) {
int totalClassNumber = classes.size();
List<DexProgramClass> sortedClasses = getTopologicalOrder(classes);
assert sortedClasses.size() == totalClassNumber;
// make a graph colorization starting by the end of the topological order (leafs)
ArrayList<ClassGroup> layers = new ArrayList<>();
Map<DexProgramClass, Integer> assignedLayer = Maps.newHashMapWithExpectedSize(classes.size());
for (int i = sortedClasses.size() - 1; i >= 0; i--) {
DexProgramClass toAssign = sortedClasses.get(i);
Collection<DexProgramClass> subClasses = directSubClasses.getDirectSubClasses(toAssign);
BitSet used = new BitSet();
for (DexProgramClass subclass : subClasses) {
used.set(assignedLayer.get(subclass).intValue());
}
// Assign the lowest available color (layer)
int layer = used.nextClearBit(0);
assignedLayer.put(toAssign, Integer.valueOf(layer));
if (layers.size() == layer) {
layers.add(new ClassGroup());
}
layers.get(layer).members.add(toAssign);
}
updateGroupsNumberOfIds(layers);
assert totalClassNumber == getTotalClassNumber(layers);
return layers;
}
/**
* Collect groups of classes with an inheritance relation. This relation can be indirect:<br>
* I3 --> I1<br>
* I4 --> I2<br>
* I5 --> I1, I2<br>
* I3 and I4 will be in the same group even if they have no relation with each other.
*/
private List<ClassGroup> collectInheritanceGroups() {
// Considering classes are the nodes of a graph which edges are the inheritance relation between
// classes. We just want to isolate every connected sub-graphs.
// To do that we just pick one node, walk through the connected sub-graph, then repeat until we
// have collected all nodes.
Collection<DexProgramClass> remainingClasses = new HashSet<>(classes);
List<ClassGroup> groups = new LinkedList<>();
while (!remainingClasses.isEmpty()) {
DexProgramClass clazz = remainingClasses.iterator().next();
ClassGroup group = new ClassGroup();
groups.add(group);
collectGroup(remainingClasses, group, clazz);
assert !group.members.isEmpty();
}
updateGroupsNumberOfIds(groups);
assert classes.size() == getTotalClassNumber(groups);
return groups;
}
private void updateGroupsNumberOfIds(List<ClassGroup> groups) {
Collection<Future<?>> updateIdsTasks = new ArrayList<>(groups.size());
for (ClassGroup group : groups) {
updateIdsTasks.add(executorService.submit(group::updateNumbersOfIds));
}
try {
ThreadUtils.awaitFutures(updateIdsTasks);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
// no checked exception thrown in task.
throw new AssertionError(e);
}
}
}
private void collectGroup(Collection<DexProgramClass> classes, ClassGroup group,
DexProgramClass clazz) {
if (clazz == null) {
return;
}
if (!classes.remove(clazz)) {
if (!group.dependsOnMainDexClasses) {
group.dependsOnMainDexClasses = mainDex.classes().contains(clazz);
}
// If the class is not in the classes list it's either that it's a non program class, part of
// the main dex or it was already added to this group: so either we're not interested in the
// class or it's dependencies and dependants are already being taken care of.
return;
}
group.members.add(clazz);
// Check dependencies are added to the group.
collectGroup(classes, group, appView.programDefinitionFor(clazz.superType, clazz));
for (DexType interfaze : clazz.interfaces.values) {
collectGroup(classes, group, appView.programDefinitionFor(interfaze, clazz));
}
// Check that dependants are added to the group.
for (DexProgramClass directSubClass : directSubClasses.getDirectSubClasses(clazz)) {
collectGroup(classes, group, directSubClass);
}
}
/**
* Assign all given classes or none.
* @return true if it managed to assign all the classes, false otherwise.
*/
private boolean assignAll(VirtualFile dex, Collection<DexProgramClass> classes) {
int totalClasses = classes.size();
int assignedClasses = 0;
int dexInitialSize = dex.classes().size();
for (DexProgramClass clazz : classes) {
dex.addClass(clazz);
assignedClasses++;
if (dex.isFull()) {
dex.abortTransaction();
if (dex.isFull(DEX_FULL_ENOUGH_THRESHOLD)) {
markDexFull(dex);
}
assert dexInitialSize == dex.classes().size();
return false;
}
}
dex.commitTransaction();
assert totalClasses == assignedClasses
&& dexInitialSize + assignedClasses == dex.classes().size();
return true;
}
/**
* Assign as many classes as possible by layer starting by roots.
* @return the list of classes that were not assigned.
*/
private Set<DexProgramClass> assignFromRoot(
VirtualFile dex, Collection<DexProgramClass> classes) {
int totalClasses = classes.size();
int assignedClasses = 0;
int dexInitialSize = dex.classes().size();
boolean isLayerFullyAssigned = true;
Set<DexProgramClass> remaining = new HashSet<>(classes);
while (isLayerFullyAssigned && !remaining.isEmpty()) {
Set<DexProgramClass> toProcess = remaining;
remaining = new HashSet<>();
boolean currentDexIsTooFull = false;
for (DexProgramClass clazz : getSortedCopy(toProcess)) {
if (currentDexIsTooFull || hasDirectInheritanceInCollection(clazz, toProcess)) {
remaining.add(clazz);
} else {
dex.addClass(clazz);
if (dex.isFull()) {
dex.abortTransaction();
if (dex.isEmpty()) {
// The class is too big to fit in one dex
throw new CompilationError("Class '" + clazz.toSourceString() + "' from "
+ clazz.getOrigin().toString() + " is too big to fit in a dex.");
}
isLayerFullyAssigned = false;
remaining.add(clazz);
if (dex.isFull(DEX_FULL_ENOUGH_THRESHOLD)) {
markDexFull(dex);
currentDexIsTooFull = true;
}
} else {
assignedClasses++;
dex.commitTransaction();
}
}
}
}
assert totalClasses == assignedClasses + remaining.size()
&& dexInitialSize + assignedClasses == dex.classes().size();
return remaining;
}
private boolean hasDirectInheritanceInCollection(DexProgramClass clazz,
Set<DexProgramClass> collection) {
if (collection.contains(appView.programDefinitionFor(clazz.superType, clazz))) {
return true;
}
for (DexType interfaze : clazz.interfaces.values) {
if (collection.contains(appView.programDefinitionFor(interfaze, clazz))) {
return true;
}
}
return false;
}
private boolean hasDirectSubclassInCollection(DexProgramClass clazz,
Set<DexProgramClass> collection) {
for (DexProgramClass subClass : directSubClasses.getDirectSubClasses(clazz)) {
if (collection.contains(subClass)) {
return true;
}
}
return false;
}
private static List<DexProgramClass> getSortedCopy(Collection<DexProgramClass> collection) {
List<DexProgramClass> sorted = new ArrayList<>(collection);
Collections.sort(sorted, DEX_PROGRAM_CLASS_COMPARATOR);
return sorted;
}
/**
* @param classes set of classes to sort, the set will be destroyed by sorting.
*/
private List<DexProgramClass> getTopologicalOrder(Set<DexProgramClass> classes) {
List<DexProgramClass> result = new ArrayList<>();
while (!classes.isEmpty()) {
DexProgramClass root = findOneRootInSetFrom(classes.iterator().next(), classes);
classes.remove(root);
result.add(root);
}
return result;
}
private DexProgramClass findOneRootInSetFrom(DexProgramClass searchFrom,
Set<DexProgramClass> classSet) {
DexProgramClass zuper = appView.programDefinitionFor(searchFrom.superType, searchFrom);
if (classSet.contains(zuper)) {
return findOneRootInSetFrom(zuper, classSet);
}
for (DexType interfaceType : searchFrom.interfaces.values) {
DexClass interfaceClass = appView.definitionFor(interfaceType);
if (interfaceClass.isProgramClass() && classSet.contains(interfaceClass.asProgramClass())) {
return findOneRootInSetFrom((DexProgramClass) interfaceClass, classSet);
}
}
return searchFrom;
}
private void markDexFull(VirtualFile dex) {
fullDex.set(dex.getId());
}
private boolean isDexFull(VirtualFile dex) {
return fullDex.get(dex.getId());
}
}