blob: aae5b5a92e7f3f73ebeadb3faf4b7ebd13a97032 [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.android.desugar;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Stream.concat;
import com.google.auto.value.AutoValue;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.devtools.build.android.desugar.io.BitFlags;
import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
import com.google.errorprone.annotations.Immutable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Remapper;
/** Helper that keeps track of which core library classes and methods we want to rewrite. */
class CoreLibrarySupport {
private static final Object[] EMPTY_FRAME = new Object[0];
private static final String[] EMPTY_LIST = new String[0];
private final CoreLibraryRewriter rewriter;
private final ClassLoader targetLoader;
/** Internal name prefixes that we want to move to a custom package. */
private final ImmutableSet<String> renamedPrefixes;
private final ImmutableSet<String> excludeFromEmulation;
/** Internal names of interfaces whose default and static interface methods we'll emulate. */
private final ImmutableSet<Class<?>> emulatedInterfaces;
/** Map from {@code owner#name} core library members to their new owners. */
private final ImmutableMap<String, String> memberMoves;
/** Map from core library types to the classes that convert to desugared types. */
private final ImmutableMap<String, String> fromConversions;
/** Map from preserved method names to the base classes that define them. */
private final ImmutableMultimap<String, String> preserveOverrides;
/** ASM {@link Remapper} based on {@link #renamedPrefixes}. */
private final Remapper corePackageRemapper =
new Remapper() {
@Override
public String map(String typeName) {
return isRenamedCoreLibrary(typeName) ? renameCoreLibrary(typeName) : typeName;
}
};
/** For the collection of definitions of emulated default methods (deterministic iteration). */
private final Multimap<String, EmulatedMethod> emulatedDefaultMethods =
LinkedHashMultimap.create();
/** Collect targets queried in {@link #getMoveTarget} and {@link #getFromCoreLibraryConverter}. */
private final Set<String> usedRuntimeHelpers = new LinkedHashSet<>();
public CoreLibrarySupport(
CoreLibraryRewriter rewriter,
ClassLoader targetLoader,
List<String> renamedPrefixes,
List<String> emulatedInterfaces,
List<String> memberMoves,
List<String> excludeFromEmulation,
List<String> fromOriginalConversions,
List<String> preserveOverrides) {
this.rewriter = rewriter;
this.targetLoader = targetLoader;
checkArgument(
renamedPrefixes.stream()
.allMatch(prefix -> prefix.startsWith("java/") || prefix.startsWith("javadesugar/")),
"Unexpected renamedPrefixes: Actual (%s).",
renamedPrefixes);
this.renamedPrefixes = ImmutableSet.copyOf(renamedPrefixes);
this.excludeFromEmulation = ImmutableSet.copyOf(excludeFromEmulation);
ImmutableSet.Builder<Class<?>> classBuilder = ImmutableSet.builder();
for (String itf : emulatedInterfaces) {
checkArgument(itf.startsWith("java/util/"), itf);
Class<?> clazz = loadFromInternal(rewriter.getPrefix() + itf);
checkArgument(clazz.isInterface(), itf);
classBuilder.add(clazz);
}
this.emulatedInterfaces = classBuilder.build();
// We can call isRenamed and rename below b/c we initialized the necessary fields above
// Use LinkedHashMap to tolerate identical duplicates
// TODO(kmb): Make map parsing code more reusable
LinkedHashMap<String, String> mapBuilder = new LinkedHashMap<>();
Splitter splitter = Splitter.on("->").trimResults().omitEmptyStrings();
for (String move : memberMoves) {
List<String> pair = splitter.splitToList(move);
checkArgument(pair.size() == 2, "Doesn't split as expected: %s", move);
int sep = pair.get(0).indexOf('#');
checkArgument(sep > 0 && sep == pair.get(0).lastIndexOf('#'), "invalid member: %s", move);
checkArgument(
!isRenamedCoreLibrary(pair.get(0).substring(0, sep)),
"Original renamed, no need to move it: %s",
move);
checkArgument(
!(pair.get(1).startsWith("java/") || pair.get(1).startsWith("javadesugar/"))
|| isRenamedCoreLibrary(pair.get(1)),
"Core library target not renamed: %s",
move);
checkArgument(
!this.excludeFromEmulation.contains(pair.get(0)),
"Retargeted invocation %s shouldn't overlap with excluded",
move);
String value = renameCoreLibrary(pair.get(1));
String existing = mapBuilder.put(pair.get(0), value);
checkArgument(
existing == null || existing.equals(value),
"Two move destinations %s and %s configured for %s",
existing,
value,
pair.get(0));
}
this.memberMoves = ImmutableMap.copyOf(mapBuilder);
splitter = Splitter.on("=").trimResults().omitEmptyStrings();
mapBuilder = new LinkedHashMap<>();
for (String fromConversion : fromOriginalConversions) {
List<String> pair = splitter.splitToList(fromConversion);
checkArgument(pair.size() == 2, "Doesn't split as expected: %s", fromConversion);
String key = pair.get(0);
String value = pair.get(1);
checkArgument(isRenamedCoreLibrary(key), "Conversion subject not renamed: %s", key);
checkArgument(!isRenamedCoreLibrary(value), "Renamed converters not supported: %s", value);
String existing = mapBuilder.put(key, value);
checkArgument(
existing == null || existing.equals(value),
"Two conversions %s and %s configured for %s",
existing,
value,
key);
}
this.fromConversions = ImmutableMap.copyOf(mapBuilder);
splitter = Splitter.on("#").trimResults().omitEmptyStrings();
ImmutableMultimap.Builder<String, String> multimapBuilder = ImmutableMultimap.builder();
for (String override : preserveOverrides) {
List<String> pair = splitter.splitToList(override);
checkArgument(pair.size() == 2, "Doesn't split as expected: %s", override);
String className = pair.get(0);
String methodName = pair.get(1);
checkArgument(
!isRenamedCoreLibrary(className),
"Conversion subject is renamed, no need to preserve: %s",
className);
multimapBuilder.put(methodName, className); // build reverse map for convenient lookups
}
this.preserveOverrides = multimapBuilder.build();
}
public boolean isRenamedCoreLibrary(String internalName) {
String unprefixedName = rewriter.unprefix(internalName);
if (!(unprefixedName.startsWith("java/") || unprefixedName.startsWith("javadesugar/"))
|| renamedPrefixes.isEmpty()) {
return false; // shortcut
}
// Rename any classes desugar might generate under java/ (for emulated interfaces) as well as
// configured prefixes
return looksGenerated(unprefixedName)
|| renamedPrefixes.stream().anyMatch(unprefixedName::startsWith);
}
public String renameCoreLibrary(String internalName) {
internalName = rewriter.unprefix(internalName);
if (internalName.startsWith("java/")) {
return "j$/" + internalName.substring(/* cut away "java/" prefix */ 5);
}
if (internalName.startsWith("javadesugar/")) {
return "jd$/" + internalName.substring(/* cut away "javadesugar/" prefix */ 12);
}
return internalName;
}
public Remapper getRemapper() {
return corePackageRemapper;
}
@Nullable
public String getMoveTarget(String owner, String name) {
String result = memberMoves.get(rewriter.unprefix(owner) + '#' + name);
if (result != null) {
// Remember that we need the move target so we can include it in the output later
usedRuntimeHelpers.add(result);
}
return result;
}
public String getFromCoreLibraryConverter(String internalName) {
String result =
checkNotNull(
fromConversions.get(rewriter.unprefix(internalName)),
"No from converter for %s",
internalName);
// Remember that we need this conversion so we can include it in the output later
usedRuntimeHelpers.add(result);
return result;
}
/**
* Indicates whether the given method should be preserved with its original descriptor b/c it
* overrides an undesugared core library method.
*/
public boolean preserveOriginalMethod(
int access, String internalName, String methodName, String descriptor) {
if (BitFlags.isStatic(access)) {
return false; // static methods don't override anything
}
if (!preserveOverrides.containsKey(methodName)) {
return false; // unknown name
}
Class<?> clazz = loadFromInternal(internalName);
if (clazz.isInterface()) {
return false; // only support preserving in classes
}
// See if clazz extends any of the configured base classes for this method
for (String baseclassName : preserveOverrides.get(methodName)) {
Class<?> baseclass = loadFromInternal(baseclassName);
checkState(
!baseclass.isInterface(), "Cannot preserve interface overrides: %s", baseclassName);
if (!baseclass.isAssignableFrom(clazz)) {
continue; // clazz must be a subclass of baseclass
}
for (Method m : clazz.getSuperclass().getMethods()) {
if (methodName.equals(m.getName())
&& descriptor.equals(Type.getMethodDescriptor(m))
&& baseclass.equals(m.getDeclaringClass())) {
// Return true if internalName directly overrides the configured method, that is,
// super.<emthodName> would call the method we want to preserve. Otherwise return false,
// which will include methods with different name, methods with different descriptors,
// methods with the same name declared in superclasses besides baseclass, and overrides of
// method we want to preserve in superclasses besides baseclass. Note in particular that
// we don't need to preserve an override if a baseclass already overrides, since the
// base class will preserve already.
return true;
}
}
}
return false;
}
/**
* Returns {@code true} for java.* classes or interfaces that are subtypes of emulated interfaces.
* Note that implies that this method always returns {@code false} for user-written classes.
*/
public boolean isEmulatedCoreClassOrInterface(String internalName) {
return getEmulatedCoreClassOrInterface(internalName) != null;
}
/** Includes the given method definition in any applicable core interface emulation logic. */
public void registerIfEmulatedCoreInterface(
int access, String owner, String name, String desc, String[] exceptions) {
Class<?> emulated = getEmulatedCoreClassOrInterface(owner);
if (emulated == null) {
return;
}
checkArgument(emulated.isInterface(), "Shouldn't be called for a class: %s.%s", owner, name);
checkArgument(
BitFlags.noneSet(
access,
Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE),
"Should only be called for default methods: %s.%s",
owner,
name);
emulatedDefaultMethods.put(
name + ":" + desc, EmulatedMethod.create(access, emulated, name, desc, exceptions));
}
/**
* If the given invocation needs to go through a companion class of an emulated or renamed core
* interface, this methods returns that interface. This is a helper method for {@link
* CoreLibraryInvocationRewriter}.
*
* <p>This method can only return non-{@code null} if {@code owner} is a core library type. It
* usually returns an emulated interface, unless the given invocation is a super-call to a core
* class's implementation of an emulated method that's being moved (other implementations of
* emulated methods in core classes are ignored). In that case the class is returned and the
* caller can use {@link #getMoveTarget} to find out where to redirect the invokespecial to.
*/
// TODO(kmb): Rethink this API and consider combining it with getMoveTarget().
@Nullable
public Class<?> getCoreInterfaceRewritingTarget(
int opcode, String owner, String name, String desc, boolean itf) {
if (looksGenerated(owner)) {
// Regular desugaring handles generated classes, no emulation is needed
return null;
}
if (!itf && opcode == Opcodes.INVOKESTATIC) {
// Ignore static invocations on classes--they never need rewriting (unless moved but that's
// handled separately).
return null;
}
if ("<init>".equals(name)) {
return null; // Constructors aren't rewritten
}
Class<?> clazz;
if (isRenamedCoreLibrary(owner)) {
// For renamed invocation targets we just need to do what InterfaceDesugaring does, that is,
// only worry about invokestatic and invokespecial interface invocations; nothing to do for
// classes and invokeinterface. InterfaceDesugaring ignores bootclasspath interfaces,
// so we have to do its work here for renamed interfaces.
if (itf && (opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL)) {
clazz = loadFromInternal(owner);
} else {
return null;
}
} else {
// If not renamed, see if the owner needs emulation.
clazz = getEmulatedCoreClassOrInterface(owner);
if (clazz == null) {
return null;
}
}
checkArgument(itf == clazz.isInterface(), "%s expected to be interface: %s", owner, itf);
if (opcode == Opcodes.INVOKESTATIC) {
// Static interface invocation always goes to the given owner
checkState(itf); // we should've bailed out above.
return clazz;
}
// See if the invoked method is a default method, which will need rewriting. For invokespecial
// we can only get here if its a default method, and invokestatic we handled above.
Method callee = findInterfaceMethod(clazz, name, desc);
if (callee != null && callee.isDefault()) {
if (isExcluded(callee)) {
return null;
}
if (!itf && opcode == Opcodes.INVOKESPECIAL) {
// See if the invoked implementation is moved; note we ignore all other overrides in classes
Class<?> impl = clazz; // we know clazz is not an interface because !itf
while (impl != null) {
String implName = impl.getName().replace('.', '/');
if (getMoveTarget(implName, name) != null) {
return impl;
}
impl = impl.getSuperclass();
}
}
Class<?> result = callee.getDeclaringClass();
if (isRenamedCoreLibrary(result.getName().replace('.', '/'))
|| emulatedInterfaces.stream().anyMatch(emulated -> emulated.isAssignableFrom(result))) {
return result;
}
// We get here if the declaring class is a supertype of an emulated interface. In that case
// use the emulated interface instead (since we don't desugar the supertype). Fail in case
// there are multiple possibilities.
Iterator<Class<?>> roots =
emulatedInterfaces.stream()
.filter(
emulated -> emulated.isAssignableFrom(clazz) && result.isAssignableFrom(emulated))
.iterator();
checkState(roots.hasNext()); // must exist
Class<?> substitute = roots.next();
checkState(!roots.hasNext(), "Ambiguous emulation substitute: %s", callee);
return substitute;
} else {
checkArgument(
!itf || opcode != Opcodes.INVOKESPECIAL,
"Couldn't resolve interface super call %s.super.%s : %s",
owner,
name,
desc);
}
return null;
}
/**
* Returns the given class if it's a core library class or interface with emulated default
* methods. This is equivalent to calling {@link #isEmulatedCoreClassOrInterface} and then just
* loading the class (using the target class loader).
*/
public Class<?> getEmulatedCoreClassOrInterface(String internalName) {
if (looksGenerated(internalName)) {
// Regular desugaring handles generated classes, no emulation is needed
return null;
}
{
String unprefixedOwner = rewriter.unprefix(internalName);
if (!unprefixedOwner.startsWith("java/util/") || isRenamedCoreLibrary(unprefixedOwner)) {
return null;
}
}
Class<?> clazz = loadFromInternal(internalName);
if (emulatedInterfaces.stream().anyMatch(itf -> itf.isAssignableFrom(clazz))) {
return clazz;
}
return null;
}
/** Returns targets queried in {@link #getMoveTarget} and {@link #getFromCoreLibraryConverter}. */
public Set<String> usedRuntimeHelpers() {
return unmodifiableSet(usedRuntimeHelpers);
}
public void makeDispatchHelpers(GeneratedClassStore store) {
LinkedHashMap<Class<?>, ClassVisitor> dispatchHelpers = new LinkedHashMap<>();
for (Collection<EmulatedMethod> group : emulatedDefaultMethods.asMap().values()) {
checkState(!group.isEmpty());
Class<?> root =
group.stream()
.map(EmulatedMethod::owner)
.max(DefaultMethodClassFixer.SubtypeComparator.INSTANCE)
.get();
checkState(
group.stream().map(m -> m.owner()).allMatch(o -> root.isAssignableFrom(o)),
"Not a single unique method: %s",
group);
String methodName = group.stream().findAny().get().name();
ImmutableList<Class<?>> customOverrides = findCustomOverrides(root, methodName);
for (EmulatedMethod methodDefinition : group) {
Class<?> owner = methodDefinition.owner();
ClassVisitor dispatchHelper =
dispatchHelpers.computeIfAbsent(
owner,
clazz -> {
String className = clazz.getName().replace('.', '/') + "$$Dispatch";
ClassVisitor result = store.add(className);
result.visit(
Opcodes.V1_7,
// Must be public so dispatch methods can be called from anywhere
Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC,
className,
/*signature=*/ null,
"java/lang/Object",
EMPTY_LIST);
return result;
});
// Types to check for before calling methodDefinition's companion, sub- before super-types
ImmutableList<Class<?>> typechecks =
concat(group.stream().map(EmulatedMethod::owner), customOverrides.stream())
.filter(o -> o != owner && owner.isAssignableFrom(o))
.distinct() // should already be but just in case
.sorted(DefaultMethodClassFixer.SubtypeComparator.INSTANCE)
.collect(ImmutableList.toImmutableList());
makeDispatchHelperMethod(dispatchHelper, methodDefinition, typechecks);
}
}
}
private ImmutableList<Class<?>> findCustomOverrides(Class<?> root, String methodName) {
ImmutableList.Builder<Class<?>> customOverrides = ImmutableList.builder();
for (ImmutableMap.Entry<String, String> move : memberMoves.entrySet()) {
// move.getKey is a string <owner>#<name> which we validated in the constructor.
// We need to take the string apart here to compare owner and name separately.
if (!methodName.equals(move.getKey().substring(move.getKey().indexOf('#') + 1))) {
continue;
}
Class<?> target =
loadFromInternal(
rewriter.getPrefix() + move.getKey().substring(0, move.getKey().indexOf('#')));
if (!root.isAssignableFrom(target)) {
continue;
}
checkState(!target.isInterface(), "can't move emulated interface method: %s", move);
customOverrides.add(target);
}
return customOverrides.build();
}
private void makeDispatchHelperMethod(
ClassVisitor helper, EmulatedMethod method, ImmutableList<Class<?>> typechecks) {
checkArgument(method.owner().isInterface());
String owner = method.owner().getName().replace('.', '/');
Type methodType = Type.getMethodType(method.descriptor());
String companionDesc =
InterfaceDesugaring.companionDefaultMethodDescriptor(owner, method.descriptor());
MethodVisitor dispatchMethod =
helper.visitMethod(
method.access() | Opcodes.ACC_STATIC,
method.name(),
companionDesc,
/*signature=*/ null, // signature is invalid due to extra "receiver" argument
method.exceptions().toArray(EMPTY_LIST));
dispatchMethod.visitCode();
{
// See if the receiver might come with its own implementation of the method, and call it.
// We do this by testing for the interface type created by EmulatedInterfaceRewriter
Label fallthrough = new Label();
String emulationInterface = renameCoreLibrary(owner);
dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver"
dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, emulationInterface);
dispatchMethod.visitJumpInsn(Opcodes.IFEQ, fallthrough);
dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver"
dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, emulationInterface);
visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */);
dispatchMethod.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
emulationInterface,
method.name(),
method.descriptor(),
/*isInterface=*/ true);
dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN));
dispatchMethod.visitLabel(fallthrough);
// Trivial frame for the branch target: same empty stack as before
dispatchMethod.visitFrame(Opcodes.F_SAME, 0, EMPTY_FRAME, 0, EMPTY_FRAME);
}
// Next, check for subtypes with specialized implementations and call them
for (Class<?> tested : typechecks) {
Label fallthrough = new Label();
String testedName = tested.getName().replace('.', '/');
// In case of a class this must be a member move; for interfaces use the companion.
String target;
String calledMethod = method.name();
if (tested.isInterface()) {
target = InterfaceDesugaring.getCompanionClassName(testedName);
calledMethod += InterfaceDesugaring.DEFAULT_COMPANION_METHOD_SUFFIX;
} else {
target = checkNotNull(memberMoves.get(rewriter.unprefix(testedName) + '#' + method.name()));
}
dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver"
dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, testedName);
dispatchMethod.visitJumpInsn(Opcodes.IFEQ, fallthrough);
dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver"
dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, testedName); // make verifier happy
visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */);
dispatchMethod.visitMethodInsn(
Opcodes.INVOKESTATIC,
target,
calledMethod,
InterfaceDesugaring.companionDefaultMethodDescriptor(testedName, method.descriptor()),
/*isInterface=*/ false);
dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN));
dispatchMethod.visitLabel(fallthrough);
// Trivial frame for the branch target: same empty stack as before
dispatchMethod.visitFrame(Opcodes.F_SAME, 0, EMPTY_FRAME, 0, EMPTY_FRAME);
}
// Call static type's default implementation in companion class
dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver"
visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */);
dispatchMethod.visitMethodInsn(
Opcodes.INVOKESTATIC,
InterfaceDesugaring.getCompanionClassName(owner),
method.name() + InterfaceDesugaring.DEFAULT_COMPANION_METHOD_SUFFIX,
companionDesc,
/*isInterface=*/ false);
dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN));
dispatchMethod.visitMaxs(0, 0);
dispatchMethod.visitEnd();
}
private boolean isExcluded(Method method) {
String unprefixedOwner =
rewriter.unprefix(method.getDeclaringClass().getName().replace('.', '/'));
return excludeFromEmulation.contains(unprefixedOwner + "#" + method.getName());
}
private Class<?> loadFromInternal(String internalName) {
try {
return targetLoader.loadClass(internalName.replace('/', '.'));
} catch (ClassNotFoundException e) {
throw (NoClassDefFoundError) new NoClassDefFoundError().initCause(e);
}
}
private static Method findInterfaceMethod(Class<?> clazz, String name, String desc) {
return collectImplementedInterfaces(clazz, new LinkedHashSet<>()).stream()
// search more subtypes before supertypes
.sorted(DefaultMethodClassFixer.SubtypeComparator.INSTANCE)
.map(itf -> findMethod(itf, name, desc))
.filter(Objects::nonNull)
.findFirst()
.orElse((Method) null);
}
private static Method findMethod(Class<?> clazz, String name, String desc) {
for (Method m : clazz.getMethods()) {
if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) {
return m;
}
}
return null;
}
private static Set<Class<?>> collectImplementedInterfaces(Class<?> clazz, Set<Class<?>> dest) {
if (clazz.isInterface()) {
if (!dest.add(clazz)) {
return dest;
}
} else if (clazz.getSuperclass() != null) {
collectImplementedInterfaces(clazz.getSuperclass(), dest);
}
for (Class<?> itf : clazz.getInterfaces()) {
collectImplementedInterfaces(itf, dest);
}
return dest;
}
/**
* Emits instructions to load a method's parameters as arguments of a method call assumed to have
* compatible descriptor, starting at the given local variable slot.
*/
private static void visitLoadArgs(MethodVisitor dispatchMethod, Type neededType, int slot) {
for (Type arg : neededType.getArgumentTypes()) {
dispatchMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot);
slot += arg.getSize();
}
}
/** Checks whether the given class is (likely) generated by desugar itself. */
private static boolean looksGenerated(String owner) {
return owner.contains("$$Lambda$") || owner.endsWith("$$CC") || owner.endsWith("$$Dispatch");
}
@AutoValue
@Immutable
abstract static class EmulatedMethod {
public static EmulatedMethod create(
int access, Class<?> owner, String name, String desc, @Nullable String[] exceptions) {
return new AutoValue_CoreLibrarySupport_EmulatedMethod(
access,
owner,
name,
desc,
exceptions != null ? ImmutableList.copyOf(exceptions) : ImmutableList.of());
}
abstract int access();
abstract Class<?> owner();
abstract String name();
abstract String descriptor();
abstract ImmutableList<String> exceptions();
}
}