blob: b7036f5219c8c2a3a4440e9c07219fccc3cf5efa [file] [log] [blame]
// 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.ir.desugar;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexAccessFlags;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
// Default and static method interface desugaring processor for classes.
// Adds default interface methods into the class when needed.
final class ClassProcessor {
private final InterfaceMethodRewriter rewriter;
// Set of already processed classes.
private final Set<DexClass> processedClasses = Sets.newIdentityHashSet();
// Maps already created methods into default methods they were generated based on.
private final Map<DexEncodedMethod, DexEncodedMethod> createdMethods = new IdentityHashMap<>();
// Caches default interface method info for already processed interfaces.
private final Map<DexType, DefaultMethodsHelper.Collection> cache = new IdentityHashMap<>();
ClassProcessor(InterfaceMethodRewriter rewriter) {
this.rewriter = rewriter;
}
final Set<DexEncodedMethod> getForwardMethods() {
return createdMethods.keySet();
}
final void process(DexClass clazz) {
if (clazz.isInterface()) {
throw new CompilationError("Interface in superclass chain.");
}
if (!clazz.isProgramClass()) {
// We assume that library classes don't need to be processed, since they
// are provided by a runtime not supporting default interface methods.
// We also skip classpath classes, which results in sub-optimal behavior
// in case classpath superclass when processed adds a default method which
// could have been reused in this class otherwise.
return;
}
if (!processedClasses.add(clazz)) {
return; // Has already been processed.
}
// Ensure superclasses are processed first. We need it since we use information
// about methods added to superclasses when we decide if we want to add a default
// method to class `clazz`.
DexType superType = clazz.superType;
if (superType != null && superType != rewriter.factory.objectType) {
process(rewriter.findRequiredClass(superType));
}
if (clazz.interfaces.isEmpty()) {
// Since superclass has already been processed and it has all missing methods
// added, these methods will be inherited by `clazz`, and only need to be revised
// in case this class has *additional* interfaces implemented, which may change
// the entire picture of the default method selection in runtime.
return;
}
// Collect the default interface methods to be added to this class.
List<DexEncodedMethod> methodsToImplement = collectMethodsToImplement(clazz);
if (methodsToImplement.isEmpty()) {
return;
}
// Add the methods.
DexEncodedMethod[] existing = clazz.virtualMethods();
clazz.setVirtualMethods(new DexEncodedMethod[existing.length + methodsToImplement.size()]);
System.arraycopy(existing, 0, clazz.virtualMethods(), 0, existing.length);
for (int i = 0; i < methodsToImplement.size(); i++) {
DexEncodedMethod method = methodsToImplement.get(i);
assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
clazz.virtualMethods()[existing.length + i] = newMethod;
createdMethods.put(newMethod, method);
}
}
private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
DexMethod method = defaultMethod.method;
assert !rewriter.findRequiredClass(method.holder).isLibraryClass();
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
DexMethod newMethod = rewriter.factory.createMethod(clazz.type, method.proto, method.name);
DexAccessFlags newFlags = new DexAccessFlags(defaultMethod.accessFlags.get());
return new DexEncodedMethod(newMethod, newFlags,
defaultMethod.annotations, defaultMethod.parameterAnnotations,
new SynthesizedCode(new ForwardMethodSourceCode(
clazz.type, method.proto, /* static method */ null,
rewriter.defaultAsMethodOfCompanionClass(method),
Invoke.Type.STATIC)));
}
// For a given class `clazz` inspects all interfaces it implements directly or
// indirectly and collect a set of all default methods to be implemented
// in this class.
private List<DexEncodedMethod> collectMethodsToImplement(DexClass clazz) {
DefaultMethodsHelper helper = new DefaultMethodsHelper();
// Collect candidate default methods by inspecting interfaces implemented
// by this class as well as its superclasses.
DexClass current = clazz;
while (true) {
for (DexType type : current.interfaces.values) {
helper.merge(getOrCreateInterfaceInfo(type));
}
DexType superType = current.superType;
if (superType == null || superType == rewriter.factory.objectType) {
// We assume here that interfaces implemented by java.lang.Object don't
// have default methods and don't hide any default interface methods since
// they must be library interfaces.
break;
}
current = rewriter.findRequiredClass(superType);
}
List<DexEncodedMethod> candidates = helper.createCandidatesList();
if (candidates.isEmpty()) {
return candidates;
}
// Remove from candidates methods defined in class or any of its superclasses.
List<DexEncodedMethod> toBeImplemented = new ArrayList<>(candidates.size());
current = clazz;
while (true) {
// Hide candidates by virtual method of the class.
hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
if (candidates.isEmpty()) {
return toBeImplemented;
}
DexType superType = current.superType;
if (superType == null || superType == rewriter.factory.objectType) {
// Note that default interface methods must never have same
// name/signature as any method in java.lang.Object (JLS ยง9.4.1.2).
// Everything still in candidate list is not hidden.
toBeImplemented.addAll(candidates);
return toBeImplemented;
}
current = rewriter.findRequiredClass(superType);
}
}
private void hideCandidates(DexEncodedMethod[] virtualMethods,
List<DexEncodedMethod> candidates, List<DexEncodedMethod> toBeImplemented) {
Iterator<DexEncodedMethod> it = candidates.iterator();
while (it.hasNext()) {
DexEncodedMethod candidate = it.next();
for (DexEncodedMethod encoded : virtualMethods) {
if (candidate.method.match(encoded)) {
// Found a methods hiding the candidate.
DexEncodedMethod basedOnCandidate = createdMethods.get(encoded);
if (basedOnCandidate != null) {
// The method we found is a method we have generated for a default interface
// method in a superclass. If the method is based on the same candidate we don't
// need to re-generate this method again since it is going to be inherited.
if (basedOnCandidate != candidate) {
// Need to re-generate since the inherited version is
// based on a different candidate.
toBeImplemented.add(candidate);
}
}
// Done with this candidate.
it.remove();
break;
}
}
}
}
private DefaultMethodsHelper.Collection getOrCreateInterfaceInfo(DexType iface) {
DefaultMethodsHelper.Collection collection = cache.get(iface);
if (collection != null) {
return collection;
}
collection = createInterfaceInfo(iface);
cache.put(iface, collection);
return collection;
}
private DefaultMethodsHelper.Collection createInterfaceInfo(DexType iface) {
DefaultMethodsHelper helper = new DefaultMethodsHelper();
DexClass clazz = rewriter.findRequiredClass(iface);
if (!clazz.isInterface()) {
throw new CompilationError(
"Type " + iface.toSourceString() + " is expected to be an interface.");
}
if (clazz.isLibraryClass()) {
// For library interfaces we always assume there are no default
// methods, since the interface is part of framework provided by
// runtime which does not support default interface methods.
return DefaultMethodsHelper.Collection.EMPTY;
}
// Merge information from all superinterfaces.
for (DexType superinterface : clazz.interfaces.values) {
helper.merge(getOrCreateInterfaceInfo(superinterface));
}
// Hide by virtual methods of this interface.
for (DexEncodedMethod virtual : clazz.virtualMethods()) {
helper.hideMatches(virtual.method);
}
// Add all default methods of this interface.
for (DexEncodedMethod encoded : clazz.virtualMethods()) {
if (rewriter.isDefaultMethod(encoded)) {
helper.addDefaultMethod(encoded);
}
}
return helper.wrapInCollection();
}
}