blob: 58bf764338ce3bb1fcfb5a38976d68fbde74d005 [file] [log] [blame]
// Copyright (c) 2016, 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.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
import com.android.tools.r8.graph.DexValue.DexValueMethodType;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.google.common.io.BaseEncoding;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
public final class DexCallSite extends IndexedDexItem {
public final DexString methodName;
public final DexProto methodProto;
public final DexMethodHandle bootstrapMethod;
public final List<DexValue> bootstrapArgs;
private DexEncodedArray encodedArray = null;
DexCallSite(DexString methodName, DexProto methodProto,
DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
assert methodName != null;
assert methodProto != null;
assert bootstrapMethod != null;
assert bootstrapArgs != null;
this.methodName = methodName;
this.methodProto = methodProto;
this.bootstrapMethod = bootstrapMethod;
this.bootstrapArgs = bootstrapArgs;
}
public int computeHashCode() {
return methodName.hashCode()
+ methodProto.hashCode() * 7
+ bootstrapMethod.hashCode() * 31
+ bootstrapArgs.hashCode() * 101;
}
public boolean computeEquals(Object other) {
if (other instanceof DexCallSite) {
DexCallSite o = (DexCallSite) other;
return methodName.equals(o.methodName)
&& methodProto.equals(o.methodProto)
&& bootstrapMethod.equals(o.bootstrapMethod)
&& bootstrapArgs.equals(o.bootstrapArgs);
}
return false;
}
public String toString() {
StringBuilder builder =
new StringBuilder("CallSite: { Name: ").append(methodName.toSourceString())
.append(", Proto: ").append(methodProto.toSourceString())
.append(", ").append(bootstrapMethod.toSourceString());
String sep = ", Args: ";
for (DexItem arg : bootstrapArgs) {
builder.append(sep).append(arg.toSourceString());
sep = ", ";
}
builder.append('}');
return builder.toString();
}
@Override
public void collectIndexedItems(IndexedItemCollection indexedItems) {
if (indexedItems.addCallSite(this)) {
methodName.collectIndexedItems(indexedItems);
methodProto.collectIndexedItems(indexedItems);
bootstrapMethod.collectIndexedItems(indexedItems);
for (DexValue arg : bootstrapArgs) {
arg.collectIndexedItems(indexedItems);
}
}
}
@Override
void collectMixedSectionItems(MixedSectionCollection mixedItems) {
mixedItems.add(getEncodedArray());
}
@Override
public int getOffset(ObjectToOffsetMapping mapping) {
return mapping.getOffsetFor(this);
}
// TODO(mikaelpeltier): Adapt syntax when invoke-custom will be available into smali.
public String toSmaliString() {
return toString();
}
public String getHash() {
return new HashBuilder().build();
}
private final class HashBuilder {
private ByteArrayOutputStream bytes;
private ObjectOutputStream out;
private void write(DexString string) throws IOException {
out.writeInt(string.size); // To avoid same-prefix problem
out.write(string.content);
}
private void write(DexType type) throws IOException {
write(type.descriptor);
}
private void write(DexMethodHandle methodHandle) throws IOException {
out.writeShort(methodHandle.type.getValue());
if (methodHandle.isFieldHandle()) {
write(methodHandle.asField());
} else {
write(methodHandle.asMethod());
}
}
private void write(DexProto proto) throws IOException {
write(proto.shorty);
write(proto.returnType);
DexType[] params = proto.parameters.values;
out.writeInt(params.length);
for (DexType param : params) {
write(param);
}
}
private void write(DexMethod method) throws IOException {
write(method.holder);
write(method.proto);
write(method.name);
}
private void write(DexField field) throws IOException {
write(field.clazz);
write(field.type);
write(field.name);
}
private void write(List<DexValue> args) throws IOException {
out.writeInt(args.size());
for (DexValue arg : args) {
// String, Class, Integer, Long, Float, Double, MethodHandle, MethodType
if (arg instanceof DexValue.DexValueString) {
out.writeByte(0);
write(((DexValue.DexValueString) arg).value);
continue;
}
if (arg instanceof DexValue.DexValueType) {
out.writeByte(1);
write(((DexValue.DexValueType) arg).value);
continue;
}
if (arg instanceof DexValue.DexValueInt) {
out.writeByte(2);
out.writeInt(((DexValue.DexValueInt) arg).value);
continue;
}
if (arg instanceof DexValue.DexValueLong) {
out.writeByte(3);
out.writeLong(((DexValue.DexValueLong) arg).value);
continue;
}
if (arg instanceof DexValue.DexValueFloat) {
out.writeByte(4);
out.writeFloat(((DexValue.DexValueFloat) arg).value);
continue;
}
if (arg instanceof DexValue.DexValueDouble) {
out.writeByte(5);
out.writeDouble(((DexValue.DexValueDouble) arg).value);
continue;
}
if (arg instanceof DexValue.DexValueMethodHandle) {
out.writeByte(6);
write(((DexValue.DexValueMethodHandle) arg).value);
continue;
}
assert arg instanceof DexValue.DexValueMethodType;
out.writeByte(7);
write(((DexValue.DexValueMethodType) arg).value);
}
}
String build() {
try {
bytes = new ByteArrayOutputStream();
out = new ObjectOutputStream(bytes);
// We will generate SHA-1 hash of the call site information based on call site
// attributes used in equality comparison, such that if the two call sites are
// different their hashes should also be different.
write(methodName);
write(methodProto);
write(bootstrapMethod);
write(bootstrapArgs);
out.close();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(bytes.toByteArray());
return BaseEncoding.base64Url().omitPadding().encode(digest.digest());
} catch (NoSuchAlgorithmException | IOException ex) {
throw new Unreachable("Cannot get SHA-1 message digest");
}
}
}
public DexEncodedArray getEncodedArray() {
if (encodedArray == null) {
// 3 is the fixed size of the call site
DexValue[] callSitesValues = new DexValue[3 + bootstrapArgs.size()];
int valuesIndex = 0;
callSitesValues[valuesIndex++] = new DexValueMethodHandle(bootstrapMethod);
callSitesValues[valuesIndex++] = new DexValueString(methodName);
callSitesValues[valuesIndex++] = new DexValueMethodType(methodProto);
for (DexValue extraArgValue : bootstrapArgs) {
callSitesValues[valuesIndex++] = extraArgValue;
}
encodedArray = new DexEncodedArray(callSitesValues);
}
return encodedArray;
}
}