blob: b3f6a31066d702571707f1e71e370424e6657406 [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.graph.AppView;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.Pair;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.objectweb.asm.Opcodes;
// Default and static method interface desugaring processor for classes.
// Adds default interface methods into the class when needed.
final class ClassProcessor {
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
private final InterfaceMethodRewriter rewriter;
private final Consumer<DexEncodedMethod> newSynthesizedMethodConsumer;
// Mapping from program and classpath classes to an information summary of forwarding methods.
private final Map<DexClass, ClassInfo> classInfo = new IdentityHashMap<>();
private final Map<DexLibraryClass, LibraryClassInfo> libraryClassInfo = new IdentityHashMap<>();
// Mapping from program and classpath interfaces to an information summary of default methods.
private final Map<DexClass, InterfaceInfo> interfaceInfo = new IdentityHashMap<>();
// Mapping from actual program classes to the synthesized forwarding methods to be created.
private final Map<DexProgramClass, List<DexEncodedMethod>> newSyntheticMethods =
new IdentityHashMap<>();
// Collection of information known at the point of a given class.
// This info is immutable and shared as it is often the same on a significant part of the
// class hierarchy. Thus, in the case of additions the parent pointer will contain prior info.
private static class ClassInfo {
static final ClassInfo EMPTY = new ClassInfo(null, ImmutableList.of(), ImmutableSet.of());
final ClassInfo parent;
final ImmutableList<DexEncodedMethod> forwardingMethods;
// The value DexType is null if no retargeting is required, and contains the type to retarget
// to if retargeting is required.
final ImmutableSet<DexEncodedMethod> desugaredLibraryForwardingMethods;
ClassInfo(
ClassInfo parent,
ImmutableList<DexEncodedMethod> forwardingMethods,
ImmutableSet<DexEncodedMethod> desugaredLibraryForwardingMethods) {
this.parent = parent;
this.forwardingMethods = forwardingMethods;
this.desugaredLibraryForwardingMethods = desugaredLibraryForwardingMethods;
}
boolean containsForwardingMethod(DexEncodedMethod method) {
return forwardingMethods.contains(method)
|| (parent != null && parent.containsForwardingMethod(method));
}
boolean containsDesugaredLibraryForwardingMethod(DexEncodedMethod method) {
return desugaredLibraryForwardingMethods.contains(method)
|| (parent != null && parent.containsDesugaredLibraryForwardingMethod(method))
|| containsMethods(forwardingMethods, method.method);
}
}
// Collection of information known at the point of a given library class.
// This information is immutable and shared in an inheritance tree. In practice most library
// classes use the empty library class info.
private static class LibraryClassInfo {
static final LibraryClassInfo EMPTY = new LibraryClassInfo(ImmutableSet.of());
final ImmutableSet<DexEncodedMethod> desugaredLibraryMethods;
LibraryClassInfo(ImmutableSet<DexEncodedMethod> desugaredLibraryMethodsToImplement) {
this.desugaredLibraryMethods = desugaredLibraryMethodsToImplement;
}
static LibraryClassInfo create(ImmutableSet<DexEncodedMethod> desugaredLibraryMethods) {
if (desugaredLibraryMethods.isEmpty()) {
return EMPTY;
}
return new LibraryClassInfo(desugaredLibraryMethods);
}
}
// Collection of information known at the point of a given interface.
// This information is mutable and a copy exist for each interface point except for the trivial
// empty case which is shared.
private static class InterfaceInfo {
static final InterfaceInfo EMPTY =
new InterfaceInfo(Collections.emptySet(), Collections.emptySet());
final Set<Wrapper<DexMethod>> defaultMethods;
final Set<DexEncodedMethod> desugaredLibraryMethods;
InterfaceInfo(
Set<Wrapper<DexMethod>> defaultMethods, Set<DexEncodedMethod> desugaredLibraryMethods) {
this.defaultMethods = defaultMethods;
this.desugaredLibraryMethods = desugaredLibraryMethods;
}
static InterfaceInfo create(
Set<Wrapper<DexMethod>> defaultMethods,
Set<DexEncodedMethod> desugaredLibraryMethodsToImplement) {
return defaultMethods.isEmpty() && desugaredLibraryMethodsToImplement.isEmpty()
? EMPTY
: new InterfaceInfo(defaultMethods, desugaredLibraryMethodsToImplement);
}
}
private static boolean containsMethods(Collection<DexEncodedMethod> methods, DexMethod method) {
for (DexEncodedMethod theMethod : methods) {
if (theMethod.method.match(method)) {
return true;
}
}
return false;
}
ClassProcessor(
AppView<?> appView,
InterfaceMethodRewriter rewriter,
Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
this.rewriter = rewriter;
this.newSynthesizedMethodConsumer = newSynthesizedMethodConsumer;
}
final void addSyntheticMethods() {
for (DexProgramClass clazz : newSyntheticMethods.keySet()) {
List<DexEncodedMethod> newForwardingMethods = newSyntheticMethods.get(clazz);
if (newForwardingMethods != null) {
clazz.appendVirtualMethods(newForwardingMethods);
}
}
}
private void addSyntheticMethod(DexProgramClass clazz, DexEncodedMethod newMethod) {
newSyntheticMethods.computeIfAbsent(clazz, key -> new ArrayList<>()).add(newMethod);
newSynthesizedMethodConsumer.accept(newMethod);
}
private ClassInfo computeClassInfo(DexType type) {
// No forwards at the top of the class hierarchy (assuming java.lang.Object is never amended).
if (type == null || type == dexItemFactory.objectType) {
return ClassInfo.EMPTY;
}
// If the type does not exist there is little we can do but assume no default methods.
DexClass clazz = appView.definitionFor(type);
if (clazz == null) {
return ClassInfo.EMPTY;
}
return computeClassInfo(clazz);
}
ClassInfo computeClassInfo(DexClass clazz) {
assert !clazz.isInterface();
return clazz.isLibraryClass()
? ClassInfo.EMPTY
: classInfo.computeIfAbsent(clazz, this::computeClassInfoRaw);
}
private ClassInfo computeClassInfoRaw(DexClass clazz) {
// We compute both library and class information, but one of them is empty, since a class is
// a library class or is not, but cannot be both.
ClassInfo superInfo = computeClassInfo(clazz.superType);
List<DexMethod> defaultMethods = computeInterfaceInfo(clazz.interfaces.values);
LibraryClassInfo superLibraryClassInfo = computeLibraryClassInfo(clazz.superType);
assert superLibraryClassInfo.desugaredLibraryMethods.isEmpty()
|| superInfo.desugaredLibraryForwardingMethods.isEmpty();
ImmutableSet<DexEncodedMethod> desugaredLibraryMethods =
computeDesugaredLibraryMethods(
clazz.interfaces.values,
superLibraryClassInfo.desugaredLibraryMethods.isEmpty()
? superInfo.desugaredLibraryForwardingMethods
: superLibraryClassInfo.desugaredLibraryMethods);
if (defaultMethods.isEmpty() && desugaredLibraryMethods.isEmpty()) {
return superInfo;
}
ImmutableList<DexEncodedMethod> forwards =
computeDefaultMethods(clazz, superInfo, defaultMethods);
ImmutableSet<DexEncodedMethod> desugaredLibraryForwardingMethods =
computeDesugaredLibraryMethods(clazz, superInfo, desugaredLibraryMethods, forwards);
if (forwards.isEmpty() && desugaredLibraryForwardingMethods.isEmpty()) {
return superInfo;
}
return new ClassInfo(superInfo, forwards, desugaredLibraryForwardingMethods);
}
private ImmutableSet<DexEncodedMethod> computeDesugaredLibraryMethods(
DexClass clazz,
ClassInfo superInfo,
ImmutableSet<DexEncodedMethod> desugaredLibraryMethods,
ImmutableList<DexEncodedMethod> forwards) {
ImmutableSet.Builder<DexEncodedMethod> additionalDesugaredLibraryForwardingMethods =
ImmutableSet.builder();
for (DexEncodedMethod dexMethod : desugaredLibraryMethods) {
if (!superInfo.containsDesugaredLibraryForwardingMethod(dexMethod)
&& !containsMethods(forwards, dexMethod.method)) {
additionalDesugaredLibraryForwardingMethods.add(dexMethod);
// We still add the methods to the list, even if override, to avoid generating it again.
if (clazz.lookupVirtualMethod(dexMethod.method) == null) {
addForwardingMethod(dexMethod, clazz);
}
}
}
return additionalDesugaredLibraryForwardingMethods.build();
}
private ImmutableList<DexEncodedMethod> computeDefaultMethods(
DexClass clazz, ClassInfo superInfo, List<DexMethod> defaultMethods) {
Builder<DexEncodedMethod> additionalForwards = ImmutableList.builder();
for (DexMethod defaultMethod : defaultMethods) {
ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, defaultMethod);
if (resolution.isFailedResolution()) {
assert resolution instanceof IncompatibleClassResult;
addICCEThrowingMethod(defaultMethod, clazz);
} else {
DexEncodedMethod target = resolution.getSingleTarget();
DexClass dexClass = appView.definitionFor(target.method.holder);
if (target.isDefaultMethod()
&& dexClass != null
&& dexClass.isInterface()
&& !superInfo.containsForwardingMethod(target)) {
additionalForwards.add(target);
addForwardingMethod(target, clazz);
}
}
}
return additionalForwards.build();
}
private LibraryClassInfo computeLibraryClassInfo(DexType type) {
// No desugaring required, no library class analysis.
if (appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
&& appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
return LibraryClassInfo.EMPTY;
}
// No forwards at the top of the class hierarchy (assuming java.lang.Object is never amended).
if (type == null || type == dexItemFactory.objectType) {
return LibraryClassInfo.EMPTY;
}
// If the type does not exist there is little we can do but assume no default methods.
DexClass clazz = appView.definitionFor(type);
if (clazz == null) {
return LibraryClassInfo.EMPTY;
}
return computeLibraryClassInfo(clazz);
}
private LibraryClassInfo computeLibraryClassInfo(DexClass clazz) {
assert !clazz.isInterface();
return clazz.isLibraryClass()
? libraryClassInfo.computeIfAbsent(clazz.asLibraryClass(), this::computeLibraryClassInfoRaw)
: LibraryClassInfo.EMPTY;
}
private LibraryClassInfo computeLibraryClassInfoRaw(DexLibraryClass clazz) {
LibraryClassInfo superInfo = computeLibraryClassInfo(clazz.superType);
ImmutableSet<DexEncodedMethod> desugaredLibraryMethods =
computeDesugaredLibraryMethods(clazz.interfaces.values, superInfo.desugaredLibraryMethods);
// Retarget method management.
for (DexEncodedMethod method : clazz.virtualMethods()) {
if (!method.isFinal()) {
Map<DexType, DexType> typeMap =
appView
.options()
.desugaredLibraryConfiguration
.getRetargetCoreLibMember()
.get(method.method.name);
if (typeMap != null) {
DexType dexType = typeMap.get(clazz.type);
if (dexType != null) {
ImmutableSet.Builder<DexEncodedMethod> newDesugaredLibraryMethods =
ImmutableSet.builder();
for (DexEncodedMethod desugaredLibraryForwardingMethod : desugaredLibraryMethods) {
if (!desugaredLibraryForwardingMethod.method.match(method.method)) {
newDesugaredLibraryMethods.add(desugaredLibraryForwardingMethod);
}
}
newDesugaredLibraryMethods.add(method);
desugaredLibraryMethods = newDesugaredLibraryMethods.build();
}
}
}
}
if (desugaredLibraryMethods == superInfo.desugaredLibraryMethods) {
return superInfo;
}
return LibraryClassInfo.create(desugaredLibraryMethods);
}
private List<DexMethod> computeInterfaceInfo(DexType[] interfaces) {
if (interfaces.length == 0) {
return Collections.emptyList();
}
Set<Wrapper<DexMethod>> defaultMethods = new HashSet<>();
for (DexType iface : interfaces) {
InterfaceInfo itfInfo = computeInterfaceInfo(iface);
defaultMethods.addAll(itfInfo.defaultMethods);
}
return defaultMethods.isEmpty()
? Collections.emptyList()
: ListUtils.map(defaultMethods, Wrapper::get);
}
private void mergeDesugaredLibraryMethods(
Map<Wrapper<DexMethod>, DexEncodedMethod> desugaredLibraryMethods,
Set<DexEncodedMethod> methods) {
for (DexEncodedMethod method : methods) {
Wrapper<DexMethod> wrap = MethodSignatureEquivalence.get().wrap(method.method);
DexEncodedMethod initialMethod = desugaredLibraryMethods.get(wrap);
if (initialMethod == null || initialMethod.method == method.method) {
desugaredLibraryMethods.put(wrap, method);
} else {
// We need to do resolution, different desugared library methods are incoming from
// interfaces/superclass without priorities.
DexClass initialHolder = appView.definitionFor(initialMethod.method.holder);
DexClass holder = appView.definitionFor(method.method.holder);
assert holder != null && initialHolder != null;
assert holder.isInterface() || initialHolder.isInterface();
if (!holder.isInterface()) {
desugaredLibraryMethods.put(wrap, method);
} else if (!initialHolder.isInterface()) {
desugaredLibraryMethods.put(wrap, initialMethod);
} else if (implementsInterface(initialHolder, holder)) {
desugaredLibraryMethods.put(wrap, initialMethod);
} else {
desugaredLibraryMethods.put(wrap, method);
}
}
}
}
private boolean implementsInterface(DexClass initialHolder, DexClass holder) {
LinkedList<DexType> workList = new LinkedList<>();
Collections.addAll(workList, initialHolder.interfaces.values);
while (!workList.isEmpty()) {
DexType dexType = workList.removeFirst();
if (dexType == holder.type) {
return true;
}
DexClass dexClass = appView.definitionFor(dexType);
assert dexClass != null;
Collections.addAll(workList, dexClass.interfaces.values);
}
return false;
}
private ImmutableSet<DexEncodedMethod> computeDesugaredLibraryMethods(
DexType[] interfaces, ImmutableSet<DexEncodedMethod> incomingDesugaredLibraryMethods) {
Map<Wrapper<DexMethod>, DexEncodedMethod> desugaredLibraryMethods = new HashMap<>();
for (DexType iface : interfaces) {
mergeDesugaredLibraryMethods(
desugaredLibraryMethods, computeInterfaceInfo(iface).desugaredLibraryMethods);
}
if (desugaredLibraryMethods.isEmpty()) {
return incomingDesugaredLibraryMethods;
}
mergeDesugaredLibraryMethods(desugaredLibraryMethods, incomingDesugaredLibraryMethods);
return desugaredLibraryMethods.isEmpty()
? ImmutableSet.of()
: ImmutableSet.copyOf(desugaredLibraryMethods.values());
}
private InterfaceInfo computeInterfaceInfo(DexType iface) {
if (iface == null || iface == dexItemFactory.objectType) {
return InterfaceInfo.EMPTY;
}
DexClass definition = appView.definitionFor(iface);
if (definition == null) {
return InterfaceInfo.EMPTY;
}
return computeInterfaceInfo(definition);
}
private InterfaceInfo computeInterfaceInfo(DexClass iface) {
return interfaceInfo.computeIfAbsent(iface, this::computeInterfaceInfoRaw);
}
private InterfaceInfo computeInterfaceInfoRaw(DexClass iface) {
assert iface.isInterface();
assert iface.superType == dexItemFactory.objectType;
Set<Wrapper<DexMethod>> defaultMethods = new HashSet<>();
Set<DexEncodedMethod> desugaredLibraryMethods = new HashSet<>();
for (DexType superiface : iface.interfaces.values) {
defaultMethods.addAll(computeInterfaceInfo(superiface).defaultMethods);
desugaredLibraryMethods.addAll(computeInterfaceInfo(superiface).desugaredLibraryMethods);
}
// NOTE: we intentionally don't desugar default methods into interface methods
// coming from android.jar since it is only possible in case v24+ version
// of android.jar is provided. The only exception is desugared library classes.
if (!iface.isLibraryClass() || appView.rewritePrefix.hasRewrittenType(iface.type)) {
for (DexEncodedMethod method : iface.methods()) {
if (method.isDefaultMethod()) {
defaultMethods.add(MethodSignatureEquivalence.get().wrap(method.method));
}
}
}
if (appView
.options()
.desugaredLibraryConfiguration
.getEmulateLibraryInterface()
.containsKey(iface.type)) {
for (DexEncodedMethod method : iface.methods()) {
if (method.isDefaultMethod() && shouldEmulate(method.method)) {
desugaredLibraryMethods.add(method);
}
}
}
return InterfaceInfo.create(defaultMethods, desugaredLibraryMethods);
}
private boolean shouldEmulate(DexMethod method) {
List<Pair<DexType, DexString>> dontRewriteInvocation =
appView.options().desugaredLibraryConfiguration.getDontRewriteInvocation();
for (Pair<DexType, DexString> pair : dontRewriteInvocation) {
if (pair.getFirst() == method.holder && pair.getSecond() == method.name) {
return false;
}
}
return true;
}
private void addICCEThrowingMethod(DexMethod method, DexClass clazz) {
if (!clazz.isProgramClass()) {
return;
}
DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
DexEncodedMethod newEncodedMethod =
new DexEncodedMethod(
newMethod,
MethodAccessFlags.fromCfAccessFlags(Opcodes.ACC_PUBLIC, false),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
new SynthesizedCode(
callerPosition ->
new ExceptionThrowingSourceCode(
clazz.type, method, callerPosition, dexItemFactory.icceType)));
addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
}
// Note: The parameter defaultMethod may be a public method on a class in case of desugared
// library retargeting (See below target.isInterface check).
private void addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
if (!clazz.isProgramClass()) {
return;
}
DexMethod method = defaultMethod.method;
DexClass target = appView.definitionFor(method.holder);
// NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
// even if this results in invalid code, these classes are never desugared.
assert target != null;
// In desugared library, emulated interface methods can be overridden by retarget lib members.
DexMethod forwardMethod =
target.isInterface()
? rewriter.defaultAsMethodOfCompanionClass(method)
: retargetMethod(method);
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
MethodAccessFlags newFlags = defaultMethod.accessFlags.copy();
// Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
newFlags.setSynthetic();
ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
ForwardMethodSourceCode.builder(newMethod);
forwardSourceCodeBuilder
.setReceiver(clazz.type)
.setTarget(forwardMethod)
.setInvokeType(Invoke.Type.STATIC)
.setIsInterface(false); // Holder is companion class, not an interface.
DexEncodedMethod newEncodedMethod =
new DexEncodedMethod(
newMethod,
newFlags,
defaultMethod.annotations,
defaultMethod.parameterAnnotationsList,
new SynthesizedCode(forwardSourceCodeBuilder::build));
addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
}
private DexMethod retargetMethod(DexMethod method) {
Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.name);
assert typeMap != null;
assert typeMap.get(method.holder) != null;
return dexItemFactory.createMethod(
typeMap.get(method.holder),
dexItemFactory.prependTypeToProto(method.holder, method.proto),
method.name);
}
}