blob: 0bdb208df94b863bfdc0820fb622b4a7e6430d05 [file] [log] [blame]
// 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.Invoke.Type;
import com.android.tools.r8.naming.NamingLens;
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> {
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.
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;
}
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 Type toInvokeType() {
assert isMethodType();
switch (this) {
case INVOKE_STATIC:
return Type.STATIC;
case INVOKE_INSTANCE:
return Type.VIRTUAL;
case INVOKE_CONSTRUCTOR:
return Type.DIRECT;
case INVOKE_DIRECT:
return Type.DIRECT;
case INVOKE_INTERFACE:
return Type.INTERFACE;
case INVOKE_SUPER:
return Type.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) {
this(type, member, isInterface, null);
}
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(IndexedItemCollection indexedItems) {
if (indexedItems.addMethodHandle(this)) {
if (member.isDexField()) {
DexField field = member.asDexField();
field.collectIndexedItems(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(indexedItems)) {
rewrittenTarget.collectIndexedItemsName(indexedItems);
}
} else {
method.collectIndexedItems(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)
.withItem(m -> m.rewrittenTarget);
}
public Handle toAsmHandle() {
return toAsmHandle(NamingLens.getIdentityLens());
}
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();
}
}
}