| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| package com.android.tools.r8.graph; | |
| import com.android.tools.r8.dex.Constants; | |
| import com.android.tools.r8.dex.IndexedItemCollection; | |
| import com.android.tools.r8.errors.Unreachable; | |
| import com.android.tools.r8.ir.code.InvokeType; | |
| import com.android.tools.r8.lightir.LirConstant; | |
| import com.android.tools.r8.naming.NamingLens; | |
| import com.android.tools.r8.utils.structural.CompareToVisitor; | |
| import com.android.tools.r8.utils.structural.HashingVisitor; | |
| import com.android.tools.r8.utils.structural.StructuralMapping; | |
| import com.android.tools.r8.utils.structural.StructuralSpecification; | |
| import java.util.Objects; | |
| import org.objectweb.asm.Handle; | |
| import org.objectweb.asm.Opcodes; | |
| public class DexMethodHandle extends IndexedDexItem | |
| implements NamingLensComparable<DexMethodHandle>, LirConstant { | |
| @Override | |
| public LirConstantOrder getLirConstantOrder() { | |
| return LirConstantOrder.METHOD_HANDLE; | |
| } | |
| @Override | |
| public int internalLirConstantAcceptCompareTo(LirConstant other, CompareToVisitor visitor) { | |
| return acceptCompareTo((DexMethodHandle) other, visitor); | |
| } | |
| @Override | |
| public void internalLirConstantAcceptHashing(HashingVisitor visitor) { | |
| acceptHashing(visitor); | |
| } | |
| public enum MethodHandleType { | |
| STATIC_PUT((short) 0x00), | |
| STATIC_GET((short) 0x01), | |
| INSTANCE_PUT((short) 0x02), | |
| INSTANCE_GET((short) 0x03), | |
| INVOKE_STATIC((short) 0x04), | |
| INVOKE_INSTANCE((short) 0x05), | |
| INVOKE_CONSTRUCTOR((short) 0x06), | |
| INVOKE_DIRECT((short) 0x07), | |
| INVOKE_INTERFACE((short) 0x08), | |
| // Internal method handle needed by lambda desugaring. | |
| // TODO(b/200254463): Remove this now that lambda desugaring is CF/CF. | |
| INVOKE_SUPER((short) 0x09); | |
| private final short value; | |
| MethodHandleType(short value) { | |
| this.value = value; | |
| } | |
| public short getValue() { | |
| return value; | |
| } | |
| public static MethodHandleType getKind(int value) { | |
| MethodHandleType kind; | |
| switch (value) { | |
| case 0x00: | |
| kind = STATIC_PUT; | |
| break; | |
| case 0x01: | |
| kind = STATIC_GET; | |
| break; | |
| case 0x02: | |
| kind = INSTANCE_PUT; | |
| break; | |
| case 0x03: | |
| kind = INSTANCE_GET; | |
| break; | |
| case 0x04: | |
| kind = INVOKE_STATIC; | |
| break; | |
| case 0x05: | |
| kind = INVOKE_INSTANCE; | |
| break; | |
| case 0x06: | |
| kind = INVOKE_CONSTRUCTOR; | |
| break; | |
| case 0x07: | |
| kind = INVOKE_DIRECT; | |
| break; | |
| case 0x08: | |
| kind = INVOKE_INTERFACE; | |
| break; | |
| case 0x09: | |
| kind = INVOKE_SUPER; | |
| break; | |
| default: | |
| throw new AssertionError(); | |
| } | |
| assert kind.getValue() == value; | |
| return kind; | |
| } | |
| @SuppressWarnings("ReferenceEquality") | |
| public static MethodHandleType fromAsmHandle( | |
| Handle handle, JarApplicationReader application, DexType clazz) { | |
| switch (handle.getTag()) { | |
| case Opcodes.H_GETFIELD: | |
| return MethodHandleType.INSTANCE_GET; | |
| case Opcodes.H_GETSTATIC: | |
| return MethodHandleType.STATIC_GET; | |
| case Opcodes.H_PUTFIELD: | |
| return MethodHandleType.INSTANCE_PUT; | |
| case Opcodes.H_PUTSTATIC: | |
| return MethodHandleType.STATIC_PUT; | |
| case Opcodes.H_INVOKESPECIAL: | |
| assert !handle.getName().equals(Constants.INSTANCE_INITIALIZER_NAME); | |
| assert !handle.getName().equals(Constants.CLASS_INITIALIZER_NAME); | |
| DexType owner = application.getTypeFromName(handle.getOwner()); | |
| if (owner == clazz) { | |
| return MethodHandleType.INVOKE_DIRECT; | |
| } else { | |
| return MethodHandleType.INVOKE_SUPER; | |
| } | |
| case Opcodes.H_INVOKEVIRTUAL: | |
| return MethodHandleType.INVOKE_INSTANCE; | |
| case Opcodes.H_INVOKEINTERFACE: | |
| return MethodHandleType.INVOKE_INTERFACE; | |
| case Opcodes.H_INVOKESTATIC: | |
| return MethodHandleType.INVOKE_STATIC; | |
| case Opcodes.H_NEWINVOKESPECIAL: | |
| return MethodHandleType.INVOKE_CONSTRUCTOR; | |
| default: | |
| throw new Unreachable("MethodHandle tag is not supported: " + handle.getTag()); | |
| } | |
| } | |
| public boolean isFieldType() { | |
| return isStaticPut() || isStaticGet() || isInstancePut() || isInstanceGet(); | |
| } | |
| public boolean isMethodType() { | |
| return isInvokeStatic() || isInvokeInstance() || isInvokeInterface() || isInvokeSuper() | |
| || isInvokeConstructor() || isInvokeDirect(); | |
| } | |
| public boolean isStaticPut() { | |
| return this == MethodHandleType.STATIC_PUT; | |
| } | |
| public boolean isStaticGet() { | |
| return this == MethodHandleType.STATIC_GET; | |
| } | |
| public boolean isInstancePut() { | |
| return this == MethodHandleType.INSTANCE_PUT; | |
| } | |
| public boolean isInstanceGet() { | |
| return this == MethodHandleType.INSTANCE_GET; | |
| } | |
| public boolean isInvokeStatic() { | |
| return this == MethodHandleType.INVOKE_STATIC; | |
| } | |
| public boolean isInvokeDirect() { | |
| return this == MethodHandleType.INVOKE_DIRECT; | |
| } | |
| public boolean isInvokeInstance() { | |
| return this == MethodHandleType.INVOKE_INSTANCE; | |
| } | |
| public boolean isInvokeInterface() { | |
| return this == MethodHandleType.INVOKE_INTERFACE; | |
| } | |
| public boolean isInvokeSuper() { | |
| return this == MethodHandleType.INVOKE_SUPER; | |
| } | |
| public boolean isInvokeConstructor() { | |
| return this == MethodHandleType.INVOKE_CONSTRUCTOR; | |
| } | |
| public InvokeType toInvokeType() { | |
| assert isMethodType(); | |
| switch (this) { | |
| case INVOKE_STATIC: | |
| return InvokeType.STATIC; | |
| case INVOKE_INSTANCE: | |
| return InvokeType.VIRTUAL; | |
| case INVOKE_CONSTRUCTOR: | |
| return InvokeType.DIRECT; | |
| case INVOKE_DIRECT: | |
| return InvokeType.DIRECT; | |
| case INVOKE_INTERFACE: | |
| return InvokeType.INTERFACE; | |
| case INVOKE_SUPER: | |
| return InvokeType.SUPER; | |
| default: | |
| throw new Unreachable( | |
| "Conversion to invoke type with unexpected method handle: " + this); | |
| } | |
| } | |
| } | |
| public final MethodHandleType type; | |
| // Field or method that the method handle is targeting. | |
| public final DexMember<? extends DexItem, ? extends DexMember<?, ?>> member; | |
| public final boolean isInterface; | |
| // If the method handle is of method type and is not an argument to a lambda metafactory | |
| // the method handle could flow to an invokeExact instruction which does equality checking | |
| // on method descriptors including the receiver. Therefore, for such method handles we | |
| // cannot perform rewriting of the receiver as that will make the invokeExact invocation | |
| // fail due to type mismatch. Therefore, fieldOrMethod will contain the method handle | |
| // as we want it in the output with the original receiver. That means that member rebinding | |
| // has not been applied to fieldOrMethod. Since renaming happens on member rebound dex methods | |
| // we need to record the member rebound target as well for naming. That is what rewrittenTarget | |
| // is for. | |
| public final DexMethod rewrittenTarget; | |
| public DexMethodHandle( | |
| MethodHandleType type, | |
| DexMember<? extends DexItem, ? extends DexMember<?, ?>> member, | |
| boolean isInterface, | |
| DexMethod rewrittenTarget) { | |
| this.type = type; | |
| this.member = member; | |
| this.isInterface = isInterface; | |
| this.rewrittenTarget = rewrittenTarget; | |
| } | |
| public static DexMethodHandle fromAsmHandle( | |
| Handle handle, JarApplicationReader application, DexType clazz) { | |
| MethodHandleType methodHandleType = MethodHandleType.fromAsmHandle(handle, application, clazz); | |
| DexMember<? extends DexItem, ? extends DexMember<?, ?>> descriptor = | |
| methodHandleType.isFieldType() | |
| ? application.getField(handle.getOwner(), handle.getName(), handle.getDesc()) | |
| : application.getMethod(handle.getOwner(), handle.getName(), handle.getDesc()); | |
| return application.getMethodHandle(methodHandleType, descriptor, handle.isInterface()); | |
| } | |
| @Override | |
| public int computeHashCode() { | |
| return Objects.hash(type, member.computeHashCode(), isInterface, rewrittenTarget); | |
| } | |
| @Override | |
| public boolean computeEquals(Object other) { | |
| if (other instanceof DexMethodHandle) { | |
| DexMethodHandle o = (DexMethodHandle) other; | |
| return type.equals(o.type) | |
| && member.equals(o.member) | |
| && (isInterface == o.isInterface) | |
| && Objects.equals(rewrittenTarget, o.rewrittenTarget); | |
| } | |
| return false; | |
| } | |
| @Override | |
| public String toString() { | |
| StringBuilder builder = | |
| new StringBuilder("MethodHandle: {") | |
| .append(type) | |
| .append(", ") | |
| .append(member.toSourceString()) | |
| .append("}"); | |
| return builder.toString(); | |
| } | |
| public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) { | |
| if (indexedItems.addMethodHandle(this)) { | |
| if (member.isDexField()) { | |
| DexField field = member.asDexField(); | |
| field.collectIndexedItems(appView, indexedItems); | |
| } else { | |
| DexMethod method = member.asDexMethod(); | |
| if (rewrittenTarget != null) { | |
| // If there is a rewritten target we need to use that to get the right name of the | |
| // targeted method (only member rebound methods take part in naming). The rest of the | |
| // indexed items are collected from method. | |
| if (method.collectIndexedItemsExceptName(appView, indexedItems)) { | |
| rewrittenTarget.collectIndexedItemsName(appView, indexedItems); | |
| } | |
| } else { | |
| method.collectIndexedItems(appView, indexedItems); | |
| } | |
| } | |
| } | |
| } | |
| @Override | |
| public int getOffset(ObjectToOffsetMapping mapping) { | |
| return mapping.getOffsetFor(this); | |
| } | |
| // TODO(mikaelpeltier): Adapt syntax when invoke-custom will be available into smali. | |
| @Override | |
| public String toSmaliString() { | |
| return toString(); | |
| } | |
| public boolean isFieldHandle() { | |
| return type.isFieldType(); | |
| } | |
| public boolean isMethodHandle() { | |
| return type.isMethodType(); | |
| } | |
| public boolean isStaticHandle() { | |
| return type.isStaticPut() || type.isStaticGet() || type.isInvokeStatic(); | |
| } | |
| public DexMethod asMethod() { | |
| assert isMethodHandle(); | |
| return (DexMethod) member; | |
| } | |
| public DexField asField() { | |
| assert isFieldHandle(); | |
| return (DexField) member; | |
| } | |
| @Override | |
| public DexMethodHandle self() { | |
| return this; | |
| } | |
| @Override | |
| public StructuralMapping<DexMethodHandle> getStructuralMapping() { | |
| return DexMethodHandle::specify; | |
| } | |
| private static void specify(StructuralSpecification<DexMethodHandle, ?> spec) { | |
| spec.withInt(m -> m.type.getValue()) | |
| .withConditionalItem(DexMethodHandle::isFieldHandle, DexMethodHandle::asField) | |
| .withConditionalItem(DexMethodHandle::isMethodHandle, DexMethodHandle::asMethod) | |
| .withBool(m -> m.isInterface) | |
| .withNullableItem(m -> m.rewrittenTarget); | |
| } | |
| public Handle toAsmHandle(NamingLens lens) { | |
| String owner; | |
| String name; | |
| String desc; | |
| boolean itf; | |
| if (isMethodHandle()) { | |
| DexMethod method = asMethod(); | |
| owner = lens.lookupInternalName(method.holder); | |
| name = | |
| rewrittenTarget != null | |
| ? lens.lookupName(rewrittenTarget).toString() | |
| : lens.lookupName(method).toString(); | |
| desc = method.proto.toDescriptorString(lens); | |
| if (method.holder.toDescriptorString().equals("Ljava/lang/invoke/LambdaMetafactory;")) { | |
| assert !isInterface; | |
| itf = false; | |
| } else { | |
| itf = isInterface; | |
| } | |
| } else { | |
| assert isFieldHandle(); | |
| DexField field = asField(); | |
| owner = lens.lookupInternalName(field.holder); | |
| name = lens.lookupName(field).toString(); | |
| desc = lens.lookupDescriptor(field.type).toString(); | |
| itf = isInterface; | |
| } | |
| return new Handle(getAsmTag(), owner, name, desc, itf); | |
| } | |
| private int getAsmTag() { | |
| switch (type) { | |
| case INVOKE_STATIC: | |
| return Opcodes.H_INVOKESTATIC; | |
| case INVOKE_CONSTRUCTOR: | |
| return Opcodes.H_NEWINVOKESPECIAL; | |
| case INVOKE_INSTANCE: | |
| return Opcodes.H_INVOKEVIRTUAL; | |
| case INVOKE_SUPER: | |
| case INVOKE_DIRECT: | |
| return Opcodes.H_INVOKESPECIAL; | |
| case STATIC_GET: | |
| return Opcodes.H_GETSTATIC; | |
| case STATIC_PUT: | |
| return Opcodes.H_PUTSTATIC; | |
| case INSTANCE_GET: | |
| return Opcodes.H_GETFIELD; | |
| case INSTANCE_PUT: | |
| return Opcodes.H_PUTFIELD; | |
| case INVOKE_INTERFACE: | |
| return Opcodes.H_INVOKEINTERFACE; | |
| default: | |
| throw new Unreachable(); | |
| } | |
| } | |
| } |