| // 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<Integer, Character>}), 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(); |
| } |
| } |