summaryrefslogtreecommitdiff
path: root/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
diff options
context:
space:
mode:
authorIvan Gavrilovic <gavra@google.com>2018-05-04 11:07:54 +0100
committerIvan Gavrilovic <gavra@google.com>2018-05-04 11:10:50 +0100
commit9d2aa110047c892318c506d8ca5db6e7e14f9189 (patch)
tree74deac1e16e97c2c13f226cd4635cd65abf19303 /java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
parent9e1602fcb030f33d2d263acd783a9e24fccb3e7d (diff)
parentbc0139bb823083b8322a3a58d3fa8eb390c154de (diff)
downloaddesugar-9e2ccbb68f4035b4cf4bad8a2580fb18cd452d79.tar.gz
* origin/upstream-master: (43 commits) Allow --worker_max_instances to take MnemonicName=value to specify max for each named worker. Clean up code that directly imports nested classes like Builder, Entry, etc. Clean up code that directly imports nested classes like Builder, Entry, etc. Clean up code that directly imports nested classes like Builder, Entry, etc. Remove use of bare Immutable{List,Map,Set} Builder classes. Relax the assertion in Desugar for checking the calls to $closeResource(...). It is possible that $closeResource(...) is not used as the calls to it might be eliminated by some optimization tools, such as Proguard. Make attempting to change --config in invocation policy an error. Remove alphabetical sorting of options in the canonical list. Remove some deprecated resources flags. Remove category checking from incompatible changes. Support source versions newer than 8 in Bazel's annotation processors stub simple core library bridge methods that only differ in return type RELNOTES: None. Reflect core library moves in super calls, even in default method stubs. Always generate default method stubs for emulated methods. RELNOTES: None. Make KeepScanner tool search classpath for nearest definition of each member reference, instead of potentially referring to a subtype. Refactor desugar's class loading machinery and related code into a separate package for easier reuse in this tool. RELNOTES: None. Minor fixes to KeepScanner tool: - use Guava to read zip entries - Fix keep rules emitted for constructors RELNOTES: None. Support custom implementations of emulated core interface methods RELNOTES: None. tests,windows: enable android.desugar.runtime emulate dynamic dispatch of emulated default interface methods RELNOTES: None. Android desugar config options to exclude methods from interface emulation RELNOTES: None. send invocations to emulated interfaces through dispatch helper. fix logic for implementing emulated interfaces. RELNOTES: None. ... BUG: none Test: existing
Diffstat (limited to 'java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java')
-rw-r--r--java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java521
1 files changed, 521 insertions, 0 deletions
diff --git a/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java b/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
new file mode 100644
index 0000000..f247074
--- /dev/null
+++ b/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java
@@ -0,0 +1,521 @@
+// 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.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.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.HashMap;
+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;
+
+/**
+ * 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;
+
+ /** For the collection of definitions of emulated default methods (deterministic iteration). */
+ private final Multimap<String, EmulatedMethod> emulatedDefaultMethods =
+ LinkedHashMultimap.create();
+
+ public CoreLibrarySupport(
+ CoreLibraryRewriter rewriter,
+ ClassLoader targetLoader,
+ List<String> renamedPrefixes,
+ List<String> emulatedInterfaces,
+ List<String> memberMoves,
+ List<String> excludeFromEmulation) {
+ this.rewriter = rewriter;
+ this.targetLoader = targetLoader;
+ checkArgument(
+ renamedPrefixes.stream().allMatch(prefix -> prefix.startsWith("java/")), 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
+ LinkedHashMap<String, String> movesBuilder = 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);
+ checkArgument(pair.get(0).startsWith("java/"), "Unexpected member: %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(isRenamedCoreLibrary(pair.get(1)), "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 = movesBuilder.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(movesBuilder);
+ }
+
+ public boolean isRenamedCoreLibrary(String internalName) {
+ String unprefixedName = rewriter.unprefix(internalName);
+ if (!unprefixedName.startsWith("java/") || 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(prefix -> unprefixedName.startsWith(prefix));
+ }
+
+ public String renameCoreLibrary(String internalName) {
+ internalName = rewriter.unprefix(internalName);
+ return (internalName.startsWith("java/"))
+ ? "j$/" + internalName.substring(/* cut away "java/" prefix */ 5)
+ : internalName;
+ }
+
+ @Nullable
+ public String getMoveTarget(String owner, String name) {
+ return memberMoves.get(rewriter.unprefix(owner) + '#' + name);
+ }
+
+ /**
+ * 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;
+ }
+
+ public void makeDispatchHelpers(GeneratedClassStore store) {
+ HashMap<Class<?>, ClassVisitor> dispatchHelpers = new HashMap<>();
+ 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(),
+ /*itf=*/ 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 =
+ tested.isInterface()
+ ? InterfaceDesugaring.getCompanionClassName(testedName)
+ : 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,
+ method.name(),
+ InterfaceDesugaring.companionDefaultMethodDescriptor(testedName, method.descriptor()),
+ /*itf=*/ 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(),
+ companionDesc,
+ /*itf=*/ 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();
+ }
+}