| package com.android.tools.r8.graph; |
| |
| import static com.google.common.base.Predicates.alwaysTrue; |
| |
| import com.android.tools.r8.utils.IterableUtils; |
| import com.android.tools.r8.utils.TraversalContinuation; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| |
| public class MethodCollection { |
| |
| // Threshold between using an array and a map for the backing store. |
| // Compiling R8 plus library shows classes with up to 30 methods account for about 95% of classes. |
| private static final int ARRAY_BACKING_THRESHOLD = 30; |
| |
| private final DexClass holder; |
| private MethodCollectionBacking backing; |
| private DexEncodedMethod cachedClassInitializer = DexEncodedMethod.SENTINEL; |
| |
| public MethodCollection( |
| DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) { |
| this.holder = holder; |
| if (directMethods.length + virtualMethods.length > ARRAY_BACKING_THRESHOLD) { |
| backing = new MethodMapBacking(); |
| } else { |
| backing = new MethodArrayBacking(); |
| } |
| backing.setDirectMethods(directMethods); |
| backing.setVirtualMethods(virtualMethods); |
| } |
| |
| private void resetCaches() { |
| resetDirectMethodCaches(); |
| resetVirtualMethodCaches(); |
| } |
| |
| private void resetDirectMethodCaches() { |
| resetClassInitializerCache(); |
| } |
| |
| private void resetVirtualMethodCaches() { |
| // Nothing to do. |
| } |
| |
| public boolean hasMethods(Predicate<DexEncodedMethod> predicate) { |
| return getMethod(predicate) != null; |
| } |
| |
| public boolean hasDirectMethods() { |
| return hasDirectMethods(alwaysTrue()); |
| } |
| |
| public boolean hasDirectMethods(Predicate<DexEncodedMethod> predicate) { |
| return backing.getDirectMethod(predicate) != null; |
| } |
| |
| public boolean hasVirtualMethods() { |
| return hasVirtualMethods(alwaysTrue()); |
| } |
| |
| public boolean hasVirtualMethods(Predicate<DexEncodedMethod> predicate) { |
| return backing.getVirtualMethod(predicate) != null; |
| } |
| |
| public int numberOfDirectMethods() { |
| return backing.numberOfDirectMethods(); |
| } |
| |
| public int numberOfVirtualMethods() { |
| return backing.numberOfVirtualMethods(); |
| } |
| |
| public int size() { |
| return backing.size(); |
| } |
| |
| public TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) { |
| return backing.traverse(fn); |
| } |
| |
| public void forEachMethod(Consumer<DexEncodedMethod> consumer) { |
| forEachMethodMatching(alwaysTrue(), consumer); |
| } |
| |
| public void forEachMethodMatching( |
| Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) { |
| backing.forEachMethod( |
| method -> { |
| if (predicate.test(method)) { |
| consumer.accept(method); |
| } |
| }); |
| } |
| |
| public void forEachDirectMethod(Consumer<DexEncodedMethod> consumer) { |
| forEachDirectMethodMatching(alwaysTrue(), consumer); |
| } |
| |
| public void forEachDirectMethodMatching( |
| Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) { |
| backing.forEachDirectMethod( |
| method -> { |
| if (predicate.test(method)) { |
| consumer.accept(method); |
| } |
| }); |
| } |
| |
| public void forEachVirtualMethod(Consumer<DexEncodedMethod> consumer) { |
| forEachVirtualMethodMatching(alwaysTrue(), consumer); |
| } |
| |
| public void forEachVirtualMethodMatching( |
| Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) { |
| backing.forEachVirtualMethod( |
| method -> { |
| if (predicate.test(method)) { |
| consumer.accept(method); |
| } |
| }); |
| } |
| |
| public Iterable<DexEncodedMethod> methods() { |
| return backing.methods(); |
| } |
| |
| public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) { |
| return IterableUtils.filter(methods(), predicate); |
| } |
| |
| public List<DexEncodedMethod> allMethodsSorted() { |
| List<DexEncodedMethod> sorted = new ArrayList<>(size()); |
| forEachMethod(sorted::add); |
| sorted.sort(Comparator.comparing(DexEncodedMember::getReference)); |
| return sorted; |
| } |
| |
| public Iterable<DexEncodedMethod> directMethods() { |
| return backing.directMethods(); |
| } |
| |
| public Iterable<DexEncodedMethod> virtualMethods() { |
| return backing.virtualMethods(); |
| } |
| |
| public DexEncodedMethod getMethod(DexMethod method) { |
| return backing.getMethod(method); |
| } |
| |
| public DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) { |
| DexEncodedMethod result = backing.getDirectMethod(predicate); |
| return result != null ? result : backing.getVirtualMethod(predicate); |
| } |
| |
| public DexEncodedMethod getDirectMethod(DexMethod method) { |
| return backing.getDirectMethod(method); |
| } |
| |
| public DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) { |
| return backing.getDirectMethod(predicate); |
| } |
| |
| public DexEncodedMethod getVirtualMethod(DexMethod method) { |
| return backing.getVirtualMethod(method); |
| } |
| |
| public DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) { |
| return backing.getVirtualMethod(predicate); |
| } |
| |
| private void resetClassInitializerCache() { |
| cachedClassInitializer = DexEncodedMethod.SENTINEL; |
| } |
| |
| public synchronized DexEncodedMethod getClassInitializer() { |
| if (cachedClassInitializer == DexEncodedMethod.SENTINEL) { |
| cachedClassInitializer = null; |
| for (DexEncodedMethod directMethod : directMethods()) { |
| if (directMethod.isClassInitializer()) { |
| cachedClassInitializer = directMethod; |
| break; |
| } |
| } |
| } |
| return cachedClassInitializer; |
| } |
| |
| public void addMethod(DexEncodedMethod method) { |
| resetCaches(); |
| backing.addMethod(method); |
| } |
| |
| public void addVirtualMethod(DexEncodedMethod virtualMethod) { |
| resetVirtualMethodCaches(); |
| backing.addVirtualMethod(virtualMethod); |
| } |
| |
| public void addDirectMethod(DexEncodedMethod directMethod) { |
| resetDirectMethodCaches(); |
| backing.addDirectMethod(directMethod); |
| } |
| |
| public DexEncodedMethod replaceDirectMethod( |
| DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| resetDirectMethodCaches(); |
| return backing.replaceDirectMethod(method, replacement); |
| } |
| |
| public DexEncodedMethod replaceVirtualMethod( |
| DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| resetVirtualMethodCaches(); |
| return backing.replaceVirtualMethod(method, replacement); |
| } |
| |
| public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| resetCaches(); |
| backing.replaceMethods(replacement); |
| } |
| |
| public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| resetDirectMethodCaches(); |
| backing.replaceDirectMethods(replacement); |
| } |
| |
| public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| resetVirtualMethodCaches(); |
| backing.replaceVirtualMethods(replacement); |
| } |
| |
| public void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| resetDirectMethodCaches(); |
| backing.replaceAllDirectMethods(replacement); |
| } |
| |
| public void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| resetVirtualMethodCaches(); |
| backing.replaceAllVirtualMethods(replacement); |
| } |
| |
| /** |
| * Replace a direct method, if found, by a computed virtual method using the replacement function. |
| * |
| * @param method Direct method to replace if present. |
| * @param replacement Replacement function computing the virtual replacement. |
| * @return Returns the replacement if found, null otherwise. |
| */ |
| public DexEncodedMethod replaceDirectMethodWithVirtualMethod( |
| DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) { |
| resetCaches(); |
| return backing.replaceDirectMethodWithVirtualMethod(method, replacement); |
| } |
| |
| public void addDirectMethods(Collection<DexEncodedMethod> methods) { |
| assert verifyCorrectnessOfMethodHolders(methods); |
| resetDirectMethodCaches(); |
| backing.addDirectMethods(methods); |
| } |
| |
| public void clearDirectMethods() { |
| resetDirectMethodCaches(); |
| backing.clearDirectMethods(); |
| } |
| |
| public DexEncodedMethod removeMethod(DexMethod method) { |
| DexEncodedMethod removed = backing.removeMethod(method); |
| if (removed != null) { |
| if (backing.belongsToDirectPool(removed)) { |
| resetDirectMethodCaches(); |
| } else { |
| assert backing.belongsToVirtualPool(removed); |
| resetVirtualMethodCaches(); |
| } |
| } |
| return removed; |
| } |
| |
| public void removeMethods(Set<DexEncodedMethod> methods) { |
| backing.removeMethods(methods); |
| resetDirectMethodCaches(); |
| resetVirtualMethodCaches(); |
| } |
| |
| public void setDirectMethods(DexEncodedMethod[] methods) { |
| assert verifyCorrectnessOfMethodHolders(methods); |
| resetDirectMethodCaches(); |
| backing.setDirectMethods(methods); |
| } |
| |
| public void setSingleDirectMethod(DexEncodedMethod method) { |
| setDirectMethods(new DexEncodedMethod[] {method}); |
| } |
| |
| public void addVirtualMethods(Collection<DexEncodedMethod> methods) { |
| assert verifyCorrectnessOfMethodHolders(methods); |
| resetVirtualMethodCaches(); |
| backing.addVirtualMethods(methods); |
| } |
| |
| public void clearVirtualMethods() { |
| resetVirtualMethodCaches(); |
| backing.clearVirtualMethods(); |
| } |
| |
| public void setVirtualMethods(DexEncodedMethod[] methods) { |
| assert verifyCorrectnessOfMethodHolders(methods); |
| resetVirtualMethodCaches(); |
| backing.setVirtualMethods(methods); |
| } |
| |
| public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) { |
| resetVirtualMethodCaches(); |
| backing.virtualizeMethods(privateInstanceMethods); |
| } |
| |
| public boolean hasAnnotations() { |
| return traverse( |
| method -> |
| method.hasAnyAnnotations() |
| ? TraversalContinuation.BREAK |
| : TraversalContinuation.CONTINUE) |
| .shouldBreak(); |
| } |
| |
| public void useSortedBacking() { |
| assert size() == 0; |
| backing = MethodMapBacking.createSorted(); |
| } |
| |
| public boolean verify() { |
| forEachMethod( |
| method -> { |
| assert verifyCorrectnessOfMethodHolder(method); |
| }); |
| assert backing.verify(); |
| return true; |
| } |
| |
| private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) { |
| assert method.getHolderType() == holder.type |
| : "Expected method `" |
| + method.getReference().toSourceString() |
| + "` to have holder `" |
| + holder.type.toSourceString() |
| + "`"; |
| return true; |
| } |
| |
| private boolean verifyCorrectnessOfMethodHolders(DexEncodedMethod[] methods) { |
| if (methods == null) { |
| return true; |
| } |
| return verifyCorrectnessOfMethodHolders(Arrays.asList(methods)); |
| } |
| |
| private boolean verifyCorrectnessOfMethodHolders(Iterable<DexEncodedMethod> methods) { |
| for (DexEncodedMethod method : methods) { |
| assert verifyCorrectnessOfMethodHolder(method); |
| } |
| return true; |
| } |
| } |