blob: 6e18ed1433af9fa61bad2302dceb34b99572966a [file] [log] [blame]
// Copyright 2016 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.lang.invoke.MethodHandles.publicLookup;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.android.desugar.io.BitFlags;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
/**
* Visitor that desugars classes with uses of lambdas into Java 7-looking code. This includes
* rewriting lambda-related invokedynamic instructions as well as fixing accessibility of methods
* that javac emits for lambda bodies.
*
* <p>Implementation note: {@link InvokeDynamicLambdaMethodCollector} needs to detect any class that
* this visitor may rewrite, as we conditionally apply this visitor based on it.
*/
class LambdaDesugaring extends ClassVisitor {
private final ClassLoader targetLoader;
private final LambdaClassMaker lambdas;
private final ImmutableSet.Builder<String> aggregateInterfaceLambdaMethods;
private final Map<Handle, MethodReferenceBridgeInfo> bridgeMethods = new LinkedHashMap<>();
private final ImmutableSet<MethodInfo> lambdaMethodsUsedInInvokeDyanmic;
private final boolean allowDefaultMethods;
private String internalName;
private boolean isInterface;
private int lambdaCount;
public LambdaDesugaring(
ClassVisitor dest,
ClassLoader targetLoader,
LambdaClassMaker lambdas,
ImmutableSet.Builder<String> aggregateInterfaceLambdaMethods,
ImmutableSet<MethodInfo> lambdaMethodsUsedInInvokeDyanmic,
boolean allowDefaultMethods) {
super(Opcodes.ASM7, dest);
this.targetLoader = targetLoader;
this.lambdas = lambdas;
this.aggregateInterfaceLambdaMethods = aggregateInterfaceLambdaMethods;
this.lambdaMethodsUsedInInvokeDyanmic = lambdaMethodsUsedInInvokeDyanmic;
this.allowDefaultMethods = allowDefaultMethods;
}
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
checkState(internalName == null, "not intended for reuse but reused for %s", name);
internalName = name;
isInterface = BitFlags.isSet(access, Opcodes.ACC_INTERFACE);
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public void visitEnd() {
for (Map.Entry<Handle, MethodReferenceBridgeInfo> bridge : bridgeMethods.entrySet()) {
Handle original = bridge.getKey();
Handle neededMethod = bridge.getValue().bridgeMethod();
checkState(
neededMethod.getTag() == Opcodes.H_INVOKESTATIC
|| neededMethod.getTag() == Opcodes.H_INVOKEVIRTUAL,
"Cannot generate bridge method %s to reach %s",
neededMethod,
original);
checkState(
bridge.getValue().referenced() != null,
"Need referenced method %s to generate bridge %s",
original,
neededMethod);
int access = Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL;
if (neededMethod.getTag() == Opcodes.H_INVOKESTATIC) {
access |= Opcodes.ACC_STATIC;
}
MethodVisitor bridgeMethod =
super.visitMethod(
access,
neededMethod.getName(),
neededMethod.getDesc(),
(String) null,
toInternalNames(bridge.getValue().referenced().getExceptionTypes()));
// Bridge is a factory method calling a constructor
if (original.getTag() == Opcodes.H_NEWINVOKESPECIAL) {
bridgeMethod.visitTypeInsn(Opcodes.NEW, original.getOwner());
bridgeMethod.visitInsn(Opcodes.DUP);
}
int slot = 0;
if (neededMethod.getTag() != Opcodes.H_INVOKESTATIC) {
bridgeMethod.visitVarInsn(Opcodes.ALOAD, slot++);
}
Type neededType = Type.getMethodType(neededMethod.getDesc());
for (Type arg : neededType.getArgumentTypes()) {
bridgeMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot);
slot += arg.getSize();
}
bridgeMethod.visitMethodInsn(
invokeOpcode(original),
original.getOwner(),
original.getName(),
original.getDesc(),
original.isInterface());
bridgeMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN));
bridgeMethod.visitMaxs(0, 0); // rely on class writer to compute these
bridgeMethod.visitEnd();
}
super.visitEnd();
}
// If this method changes then InvokeDynamicLambdaMethodCollector may need changes well
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("$deserializeLambda$") && BitFlags.isSet(access, Opcodes.ACC_SYNTHETIC)) {
// Android doesn't do anything special for lambda serialization so drop the special
// deserialization hook that javac generates. This also makes sure we don't reference
// java/lang/invoke/SerializedLambda, which doesn't exist on Android.
return null;
}
if (name.startsWith("lambda$")
&& BitFlags.isSet(access, Opcodes.ACC_SYNTHETIC)
&& lambdaMethodsUsedInInvokeDyanmic.contains(MethodInfo.create(internalName, name, desc))) {
if (!allowDefaultMethods && isInterface && BitFlags.isSet(access, Opcodes.ACC_STATIC)) {
// There must be a lambda in the interface (which in the absence of hand-written default or
// static interface methods must mean it's in the <clinit> method or inside another lambda).
// We'll move this method out of this class, so just record and drop it here.
// (Note lambda body methods have unique names, so we don't need to remember desc here.)
aggregateInterfaceLambdaMethods.add(internalName + '#' + name);
return null;
}
if (BitFlags.isSet(access, Opcodes.ACC_PRIVATE)) {
// Make lambda body method accessible from lambda class
access &= ~Opcodes.ACC_PRIVATE;
if (allowDefaultMethods && isInterface) {
// java 8 requires interface methods to have exactly one of ACC_PUBLIC and ACC_PRIVATE
access |= Opcodes.ACC_PUBLIC;
} else {
// Method was private so it can be final, which should help VMs perform dispatch.
access |= Opcodes.ACC_FINAL;
}
}
// Guarantee unique lambda body method name to avoid accidental overriding. This wouldn't be
// be necessary for static methods but in visitOuterClass we don't know whether a potential
// outer lambda$ method is static or not, so we just always do it.
name = uniqueInPackage(internalName, name);
}
MethodVisitor dest = super.visitMethod(access, name, desc, signature, exceptions);
return dest != null
? new InvokedynamicRewriter(dest, access, name, desc, signature, exceptions)
: null;
}
// If this method changes then InvokeDynamicLambdaMethodCollector may need changes well
@Override
public void visitOuterClass(String owner, String name, String desc) {
if (name != null && name.startsWith("lambda$")) {
// Reflect renaming of lambda$ methods. Proguard gets grumpy if we leave this inconsistent.
name = uniqueInPackage(owner, name);
}
super.visitOuterClass(owner, name, desc);
}
// When adding visitXxx methods here then InvokeDynamicLambdaMethodCollector may need changes well
static String uniqueInPackage(String owner, String name) {
String suffix = "$" + owner.substring(owner.lastIndexOf('/') + 1);
// For idempotency, we only attach the package-unique suffix if it isn't there already. This
// prevents a cumulative effect when processing a class more than once (which can happen with
// Bazel, e.g., when re-importing a deploy.jar). During reprocessing, invokedynamics are
// already removed, so lambda$ methods have regular call sites that we would also have to re-
// adjust if we just blindly appended something to lambda$ method names every time we see them.
return name.endsWith(suffix) ? name : name + suffix;
}
/**
* Makes {@link #visitEnd} generate a bridge method for the given method handle if the referenced
* method will be invisible to the generated lambda class.
*
* @return struct containing either {@code invokedMethod} or {@code invokedMethod} and a handle
* representing the bridge method that will be generated for {@code invokedMethod}.
*/
private MethodReferenceBridgeInfo queueUpBridgeMethodIfNeeded(Handle invokedMethod)
throws ClassNotFoundException {
if (invokedMethod.getName().startsWith("lambda$")) {
// We adjust lambda bodies to be visible
return MethodReferenceBridgeInfo.noBridge(invokedMethod);
}
// invokedMethod is a method reference if we get here
Executable invoked = findTargetMethod(invokedMethod);
if (isVisibleToLambdaClass(invoked, invokedMethod.getOwner())) {
// Referenced method is visible to the generated class, so nothing to do
return MethodReferenceBridgeInfo.noBridge(invokedMethod);
}
// We need a bridge method if we get here
checkState(
!isInterface,
"%s is an interface and shouldn't need bridge to %s",
internalName,
invokedMethod);
checkState(
!invokedMethod.isInterface(),
"%s's lambda classes can't see interface method: %s",
internalName,
invokedMethod);
MethodReferenceBridgeInfo result = bridgeMethods.get(invokedMethod);
if (result != null) {
return result; // we're already queued up a bridge method for this method reference
}
String name = uniqueInPackage(internalName, "bridge$lambda$" + bridgeMethods.size());
Handle bridgeMethod;
switch (invokedMethod.getTag()) {
case Opcodes.H_INVOKESTATIC:
bridgeMethod =
new Handle(
invokedMethod.getTag(), internalName, name, invokedMethod.getDesc(), /*itf*/ false);
break;
case Opcodes.H_INVOKEVIRTUAL:
case Opcodes.H_INVOKESPECIAL: // we end up calling these using invokevirtual
bridgeMethod =
new Handle(
Opcodes.H_INVOKEVIRTUAL,
internalName,
name,
invokedMethod.getDesc(), /*itf*/
false);
break;
case Opcodes.H_NEWINVOKESPECIAL:
{
// Call invisible constructor through generated bridge "factory" method, so we need to
// compute the descriptor for the bridge method from the constructor's descriptor
String desc =
Type.getMethodDescriptor(
Type.getObjectType(invokedMethod.getOwner()),
Type.getArgumentTypes(invokedMethod.getDesc()));
bridgeMethod =
new Handle(Opcodes.H_INVOKESTATIC, internalName, name, desc, /*itf*/ false);
break;
}
case Opcodes.H_INVOKEINTERFACE:
// Shouldn't get here
default:
throw new UnsupportedOperationException("Cannot bridge " + invokedMethod);
}
result = MethodReferenceBridgeInfo.bridge(invokedMethod, invoked, bridgeMethod);
MethodReferenceBridgeInfo old = bridgeMethods.put(invokedMethod, result);
checkState(old == null, "Already had bridge %s so we don't also want %s", old, result);
return result;
}
/**
* Checks whether the referenced method would be visible by an unrelated class in the same package
* as the currently visited class.
*/
private boolean isVisibleToLambdaClass(Executable invoked, String owner) {
int modifiers = invoked.getModifiers();
if (Modifier.isPrivate(modifiers)) {
return false;
}
if (Modifier.isPublic(modifiers)) {
return true;
}
// invoked is protected or package-private, either way we need it to be in the same package
// because the additional visibility protected gives doesn't help lambda classes, which are in
// a different class hierarchy (and typically just extend Object)
return packageName(internalName).equals(packageName(owner));
}
private Executable findTargetMethod(Handle invokedMethod) throws ClassNotFoundException {
Type descriptor = Type.getMethodType(invokedMethod.getDesc());
Class<?> owner = loadFromInternal(invokedMethod.getOwner());
if (invokedMethod.getTag() == Opcodes.H_NEWINVOKESPECIAL) {
for (Constructor<?> c : owner.getDeclaredConstructors()) {
if (Type.getType(c).equals(descriptor)) {
return c;
}
}
} else {
for (Method m : owner.getDeclaredMethods()) {
if (m.getName().equals(invokedMethod.getName()) && Type.getType(m).equals(descriptor)) {
return m;
}
}
}
throw new IllegalArgumentException("Referenced method not found: " + invokedMethod);
}
private Class<?> loadFromInternal(String internalName) throws ClassNotFoundException {
return targetLoader.loadClass(internalName.replace('/', '.'));
}
static int invokeOpcode(Handle invokedMethod) {
switch (invokedMethod.getTag()) {
case Opcodes.H_INVOKESTATIC:
return Opcodes.INVOKESTATIC;
case Opcodes.H_INVOKEVIRTUAL:
return Opcodes.INVOKEVIRTUAL;
case Opcodes.H_INVOKESPECIAL:
case Opcodes.H_NEWINVOKESPECIAL: // Must be preceded by NEW
return Opcodes.INVOKESPECIAL;
case Opcodes.H_INVOKEINTERFACE:
return Opcodes.INVOKEINTERFACE;
default:
throw new UnsupportedOperationException("Don't know how to call " + invokedMethod);
}
}
private static String[] toInternalNames(Class<?>[] classes) {
String[] result = new String[classes.length];
for (int i = 0; i < classes.length; ++i) {
result[i] = Type.getInternalName(classes[i]);
}
return result;
}
private static String packageName(String internalClassName) {
int lastSlash = internalClassName.lastIndexOf('/');
return lastSlash > 0 ? internalClassName.substring(0, lastSlash) : "";
}
/**
* Desugaring that replaces invokedynamics for {@link java.lang.invoke.LambdaMetafactory} with
* static factory method invocations and triggers a class to be generated for each invokedynamic.
*/
private class InvokedynamicRewriter extends MethodNode {
private final MethodVisitor dest;
public InvokedynamicRewriter(
MethodVisitor dest,
int access,
String name,
String desc,
String signature,
String[] exceptions) {
super(Opcodes.ASM7, access, name, desc, signature, exceptions);
this.dest = checkNotNull(dest, "Null destination for %s.%s : %s", internalName, name, desc);
}
@Override
public void visitEnd() {
accept(dest);
}
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
if (!"java/lang/invoke/LambdaMetafactory".equals(bsm.getOwner())) {
// Not an invokedynamic for a lambda expression
super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
return;
}
try {
Lookup lookup = createLookup(internalName);
ArrayList<Object> args = new ArrayList<>(bsmArgs.length + 3);
args.add(lookup);
args.add(name);
args.add(MethodType.fromMethodDescriptorString(desc, targetLoader));
for (Object bsmArg : bsmArgs) {
args.add(toJvmMetatype(lookup, bsmArg));
}
// Both bootstrap methods in LambdaMetafactory expect a MethodHandle as their 5th argument
// so we can assume bsmArgs[1] (the 5th arg) to be a Handle.
MethodReferenceBridgeInfo bridgeInfo = queueUpBridgeMethodIfNeeded((Handle) bsmArgs[1]);
// Resolve the bootstrap method in "host configuration" (this tool's default classloader)
// since targetLoader may only contain stubs that we can't actually execute.
// generateLambdaClass() below will invoke the bootstrap method, so a stub isn't enough,
// and ultimately we don't care if the bootstrap method was even on the bootclasspath
// when this class was compiled (although it must've been since javac is unhappy otherwise).
MethodHandle bsmMethod = toMethodHandle(publicLookup(), bsm, /*target*/ false);
// Give generated classes to have more stable names (b/35643761). Use BSM's naming scheme
// but with separate counter for each surrounding class.
String lambdaClassName = internalName + "$$Lambda$" + (lambdaCount++);
Type[] capturedTypes = Type.getArgumentTypes(desc);
boolean needFactory =
capturedTypes.length != 0
&& !attemptAllocationBeforeArgumentLoads(lambdaClassName, capturedTypes);
lambdas.generateLambdaClass(
internalName,
LambdaInfo.create(
lambdaClassName,
desc,
needFactory,
bridgeInfo.methodReference(),
bridgeInfo.bridgeMethod()),
bsmMethod,
args);
if (desc.startsWith("()")) {
// For stateless lambda classes we'll generate a singleton instance that we can just load
checkState(capturedTypes.length == 0);
super.visitFieldInsn(
Opcodes.GETSTATIC,
lambdaClassName,
LambdaClassFixer.SINGLETON_FIELD_NAME,
desc.substring("()".length()));
} else if (needFactory) {
// If we were unable to inline the allocation of the generated lambda class then
// invoke factory method of generated lambda class with the arguments on the stack
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
lambdaClassName,
LambdaClassFixer.FACTORY_METHOD_NAME,
desc,
/*itf*/ false);
} else {
// Otherwise we inserted a new/dup pair of instructions above and now just need to invoke
// the constructor of generated lambda class with the arguments on the stack
super.visitMethodInsn(
Opcodes.INVOKESPECIAL,
lambdaClassName,
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, capturedTypes),
/*itf*/ false);
}
} catch (IOException | ReflectiveOperationException e) {
throw new IllegalStateException(
"Couldn't desugar invokedynamic for "
+ internalName
+ "."
+ name
+ " using "
+ bsm
+ " with arguments "
+ Arrays.toString(bsmArgs),
e);
}
}
/**
* Tries to insert a new/dup for the given class name before expected existing instructions that
* set up arguments for an invokedynamic factory method with the given types.
*
* <p>For lambda expressions and simple method references we can assume that arguments are set
* up with loads of the captured (effectively) final variables. But method references, can in
* general capture an expression, such as in {@code myObject.toString()::charAt} (a {@code
* Function&lt;Integer, Character&gt;}), which can also cause null checks to be inserted. In
* such more complicated cases this method may fail to insert a new/dup pair and returns {@code
* false}.
*
* @param internalName internal name of the class to instantiate
* @param paramTypes expected invokedynamic argument types, which also must be the parameters of
* {@code internalName}'s constructor.
* @return {@code true} if we were able to insert a new/dup, {@code false} otherwise
*/
private boolean attemptAllocationBeforeArgumentLoads(String internalName, Type[] paramTypes) {
checkArgument(paramTypes.length > 0, "Expected at least one param for %s", internalName);
// Walk backwards past loads corresponding to constructor arguments to find the instruction
// after which we need to insert our NEW/DUP pair
AbstractInsnNode insn = instructions.getLast();
for (int i = paramTypes.length - 1; 0 <= i; --i) {
if (insn.getOpcode() == Opcodes.GETFIELD) {
// Lambdas in anonymous inner classes have to load outer scope variables from fields,
// which manifest as an ALOAD followed by one or more GETFIELDs
FieldInsnNode getfield = (FieldInsnNode) insn;
checkState(
getfield.desc.length() == 1
? getfield.desc.equals(paramTypes[i].getDescriptor())
: paramTypes[i].getDescriptor().length() > 1,
"Expected getfield for %s to set up parameter %s for %s but got %s : %s",
paramTypes[i],
i,
internalName,
getfield.name,
getfield.desc);
insn = insn.getPrevious();
while (insn.getOpcode() == Opcodes.GETFIELD) {
// Nested inner classes can cause a cascade of getfields from the outermost one inwards
checkState(
((FieldInsnNode) insn).desc.startsWith("L"),
"expect object type getfields to get to %s to set up parameter %s for %s, not: %s",
paramTypes[i],
i,
internalName,
((FieldInsnNode) insn).desc);
insn = insn.getPrevious();
}
checkState(
insn.getOpcode() == Opcodes.ALOAD, // should be a this pointer to be precise
"Expected aload before getfield for %s to set up parameter %s for %s but got %s",
getfield.name,
i,
internalName,
insn.getOpcode());
} else if (!isPushForType(insn, paramTypes[i])) {
// Otherwise expect load of a (effectively) final local variable or a constant. Not seeing
// that means we're dealing with a method reference on some arbitrary expression,
// <expression>::m. In that case we give up and keep using the factory method for now,
// since inserting the NEW/DUP so the new object ends up in the right stack slot is hard
// in that case. Note this still covers simple cases such as this::m or x::m, where x is a
// local.
checkState(
paramTypes.length == 1,
"Expected a load for %s to set up parameter %s for %s but got %s",
paramTypes[i],
i,
internalName,
insn.getOpcode());
return false;
}
insn = insn.getPrevious();
}
TypeInsnNode newInsn = new TypeInsnNode(Opcodes.NEW, internalName);
if (insn == null) {
// Ran off the front of the instruction list
instructions.insert(newInsn);
} else {
instructions.insert(insn, newInsn);
}
instructions.insert(newInsn, new InsnNode(Opcodes.DUP));
return true;
}
/**
* Returns whether a given instruction can be used to push argument of {@code type} on stack.
*/
private /* static */ boolean isPushForType(AbstractInsnNode insn, Type type) {
int opcode = insn.getOpcode();
if (opcode == type.getOpcode(Opcodes.ILOAD)) {
return true;
}
// b/62060793: AsyncAwait rewrites bytecode to convert java methods into state machine with
// support of lambdas. Constant zero values are pushed on stack for all yet uninitialized
// local variables. And SIPUSH instruction is used to advance an internal state of a state
// machine.
switch (type.getSort()) {
case Type.BOOLEAN:
return opcode == Opcodes.ICONST_0 || opcode == Opcodes.ICONST_1;
case Type.BYTE:
case Type.CHAR:
case Type.SHORT:
case Type.INT:
return opcode == Opcodes.SIPUSH
|| opcode == Opcodes.ICONST_0
|| opcode == Opcodes.ICONST_1
|| opcode == Opcodes.ICONST_2
|| opcode == Opcodes.ICONST_3
|| opcode == Opcodes.ICONST_4
|| opcode == Opcodes.ICONST_5
|| opcode == Opcodes.ICONST_M1;
case Type.LONG:
return opcode == Opcodes.LCONST_0 || opcode == Opcodes.LCONST_1;
case Type.FLOAT:
return opcode == Opcodes.FCONST_0
|| opcode == Opcodes.FCONST_1
|| opcode == Opcodes.FCONST_2;
case Type.DOUBLE:
return opcode == Opcodes.DCONST_0 || opcode == Opcodes.DCONST_1;
case Type.OBJECT:
case Type.ARRAY:
return opcode == Opcodes.ACONST_NULL;
default:
// Support for BIPUSH and LDC* opcodes is not implemented as there is no known use case.
return false;
}
}
private Lookup createLookup(String lookupClass) throws ReflectiveOperationException {
Class<?> clazz = loadFromInternal(lookupClass);
Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class);
constructor.setAccessible(true);
return constructor.newInstance(clazz);
}
/**
* Produces a {@link MethodHandle} or {@link MethodType} using {@link #targetLoader} for the
* given ASM {@link Handle} or {@link Type}. {@code lookup} is only used for resolving {@link
* Handle}s.
*/
private Object toJvmMetatype(Lookup lookup, Object asm) throws ReflectiveOperationException {
if (asm instanceof Number) {
return asm;
}
if (asm instanceof Type) {
Type type = (Type) asm;
switch (type.getSort()) {
case Type.OBJECT:
return loadFromInternal(type.getInternalName());
case Type.METHOD:
return MethodType.fromMethodDescriptorString(type.getDescriptor(), targetLoader);
default:
throw new IllegalArgumentException("Cannot convert: " + asm);
}
}
if (asm instanceof Handle) {
return toMethodHandle(lookup, (Handle) asm, /*target*/ true);
}
throw new IllegalArgumentException("Cannot convert: " + asm);
}
/**
* Produces a {@link MethodHandle} using either the context or {@link #targetLoader} class
* loader, depending on {@code target}.
*/
private MethodHandle toMethodHandle(Lookup lookup, Handle asmHandle, boolean target)
throws ReflectiveOperationException {
Class<?> owner = loadFromInternal(asmHandle.getOwner());
MethodType signature =
MethodType.fromMethodDescriptorString(
asmHandle.getDesc(),
target ? targetLoader : Thread.currentThread().getContextClassLoader());
switch (asmHandle.getTag()) {
case Opcodes.H_INVOKESTATIC:
return lookup.findStatic(owner, asmHandle.getName(), signature);
case Opcodes.H_INVOKEVIRTUAL:
case Opcodes.H_INVOKEINTERFACE:
return lookup.findVirtual(owner, asmHandle.getName(), signature);
case Opcodes.H_INVOKESPECIAL: // we end up calling these using invokevirtual
return lookup.findSpecial(owner, asmHandle.getName(), signature, owner);
case Opcodes.H_NEWINVOKESPECIAL:
return lookup.findConstructor(owner, signature);
default:
throw new UnsupportedOperationException("Cannot resolve " + asmHandle);
}
}
}
/**
* Record of how a lambda class can reach its referenced method through a possibly-different
* bridge method.
*
* <p>In a JVM, lambda classes are allowed to call the referenced methods directly, but we don't
* have that luxury when the generated lambda class is evaluated using normal visibility rules.
*/
@AutoValue
abstract static class MethodReferenceBridgeInfo {
public static MethodReferenceBridgeInfo noBridge(Handle methodReference) {
return new AutoValue_LambdaDesugaring_MethodReferenceBridgeInfo(
methodReference, (Executable) null, methodReference);
}
public static MethodReferenceBridgeInfo bridge(
Handle methodReference, Executable referenced, Handle bridgeMethod) {
checkArgument(!bridgeMethod.equals(methodReference));
return new AutoValue_LambdaDesugaring_MethodReferenceBridgeInfo(
methodReference, checkNotNull(referenced), bridgeMethod);
}
public abstract Handle methodReference();
/** Returns {@code null} iff {@link #bridgeMethod} equals {@link #methodReference}. */
@Nullable
public abstract Executable referenced();
public abstract Handle bridgeMethod();
}
}