// 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 bootstrapArgs; private DexEncodedArray encodedArray = null; DexCallSite(DexString methodName, DexProto methodProto, DexMethodHandle bootstrapMethod, List 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 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; } }