blob: 22d0c6d27b1cfee6968eea65232c3d3812d928a7 [file] [log] [blame]
// Copyright (c) 2021, 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.androidapi;
import static com.android.tools.r8.lightir.ByteUtils.isU2;
import static com.android.tools.r8.utils.AndroidApiLevel.ANDROID_PLATFORM;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThrowingFunction;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class AndroidApiLevelHashingDatabaseImpl implements AndroidApiLevelDatabase {
private static final byte TYPE_IDENTIFIER = 0;
private static final byte FIELD_IDENTIFIER = 1;
private static final byte METHOD_IDENTIFIER = 2;
private static final byte[] NON_EXISTING_DESCRIPTOR = new byte[0];
public static byte[] getNonExistingDescriptor() {
return NON_EXISTING_DESCRIPTOR;
}
public static byte[] getUniqueDescriptorForReference(
DexReference reference, ThrowingFunction<DexString, Integer, IOException> constantPoolLookup)
throws IOException {
if (reference.isDexType()) {
return typeToBytes(constantPoolLookup.apply(reference.asDexType().getDescriptor()));
}
int holderId =
constantPoolLookup.apply(reference.asDexMember().getHolderType().getDescriptor());
if (holderId < 0) {
return NON_EXISTING_DESCRIPTOR;
}
int nameId = constantPoolLookup.apply(reference.asDexMember().getName());
if (nameId < 0) {
return NON_EXISTING_DESCRIPTOR;
}
if (reference.isDexField()) {
return fieldToBytes(
holderId,
nameId,
constantPoolLookup.apply(reference.asDexField().getType().getDescriptor()));
}
assert reference.isDexMethod();
return methodToBytes(holderId, nameId, reference.asDexMethod(), constantPoolLookup);
}
private static byte[] typeToBytes(int typeId) {
if (typeId < 0) {
return NON_EXISTING_DESCRIPTOR;
}
return new byte[] {
TYPE_IDENTIFIER, getFirstByteFromShort(typeId), getSecondByteFromShort(typeId)
};
}
private static byte[] fieldToBytes(int holderId, int nameId, int typeId) {
if (holderId < 0 || nameId < 0 || typeId < 0) {
return NON_EXISTING_DESCRIPTOR;
}
return new byte[] {
FIELD_IDENTIFIER,
getFirstByteFromShort(holderId),
getSecondByteFromShort(holderId),
getFirstByteFromShort(nameId),
getSecondByteFromShort(nameId),
getFirstByteFromShort(typeId),
getSecondByteFromShort(typeId)
};
}
private static byte[] methodToBytes(
int holderId,
int nameId,
DexMethod method,
ThrowingFunction<DexString, Integer, IOException> constantPoolLookup)
throws IOException {
if (holderId < 0 || nameId < 0) {
return NON_EXISTING_DESCRIPTOR;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(METHOD_IDENTIFIER);
baos.write(getFirstByteFromShort(holderId));
baos.write(getSecondByteFromShort(holderId));
baos.write(getFirstByteFromShort(nameId));
baos.write(getSecondByteFromShort(nameId));
for (DexType parameter : method.proto.parameters) {
int parameterId = constantPoolLookup.apply(parameter.getDescriptor());
if (parameterId < 0) {
return NON_EXISTING_DESCRIPTOR;
}
baos.write(getFirstByteFromShort(parameterId));
baos.write(getSecondByteFromShort(parameterId));
}
int returnTypeId = constantPoolLookup.apply(method.getReturnType().getDescriptor());
if (returnTypeId < 0) {
return NON_EXISTING_DESCRIPTOR;
}
baos.write(getFirstByteFromShort(returnTypeId));
baos.write(getSecondByteFromShort(returnTypeId));
return baos.toByteArray();
}
private static byte getFirstByteFromShort(int value) {
assert isU2(value);
return (byte) (value >> 8);
}
private static byte getSecondByteFromShort(int value) {
assert isU2(value);
return (byte) value;
}
private final Map<DexReference, AndroidApiLevel> lookupCache = new ConcurrentHashMap<>();
private final Map<DexString, Integer> constantPoolCache = new ConcurrentHashMap<>();
private final InternalOptions options;
private final DiagnosticsHandler diagnosticsHandler;
private static volatile AndroidApiDataAccess dataAccess;
private static AndroidApiDataAccess getDataAccess(
InternalOptions options, DiagnosticsHandler diagnosticsHandler) {
if (dataAccess == null) {
synchronized (AndroidApiDataAccess.class) {
if (dataAccess == null) {
dataAccess = AndroidApiDataAccess.create(options, diagnosticsHandler);
}
}
}
return dataAccess;
}
public AndroidApiLevelHashingDatabaseImpl(
List<AndroidApiForHashingReference> predefinedApiTypeLookup,
InternalOptions options,
DiagnosticsHandler diagnosticsHandler) {
this.options = options;
this.diagnosticsHandler = diagnosticsHandler;
predefinedApiTypeLookup.forEach(
predefinedApiReference -> {
// Do not use computeIfAbsent since a return value of null implies the key should not be
// inserted.
lookupCache.put(
predefinedApiReference.getReference(), predefinedApiReference.getApiLevel());
});
assert predefinedApiTypeLookup.stream()
.allMatch(added -> added.getApiLevel().isEqualTo(lookupApiLevel(added.getReference())));
}
@Override
public AndroidApiLevel getTypeApiLevel(DexType type) {
return lookupApiLevel(type);
}
@Override
public AndroidApiLevel getMethodApiLevel(DexMethod method) {
return lookupApiLevel(method);
}
@Override
public AndroidApiLevel getFieldApiLevel(DexField field) {
return lookupApiLevel(field);
}
private int getConstantPoolId(DexString string) {
return constantPoolCache.computeIfAbsent(
string, key -> getDataAccess(options, diagnosticsHandler).getConstantPoolIndex(string));
}
private AndroidApiLevel lookupApiLevel(DexReference reference) {
// We use Android platform to track if an element is unknown since no occurrences of that api
// level exists in the database.
AndroidApiLevel result =
lookupCache.computeIfAbsent(
reference,
ref -> {
byte[] uniqueDescriptorForReference;
try {
uniqueDescriptorForReference =
getUniqueDescriptorForReference(ref, this::getConstantPoolId);
} catch (Exception e) {
uniqueDescriptorForReference = getNonExistingDescriptor();
}
if (uniqueDescriptorForReference == getNonExistingDescriptor()) {
return ANDROID_PLATFORM;
} else {
byte apiLevelForReference =
getDataAccess(options, diagnosticsHandler)
.getApiLevelForReference(uniqueDescriptorForReference, ref);
return (apiLevelForReference <= 0)
? ANDROID_PLATFORM
: AndroidApiLevel.getAndroidApiLevel(apiLevelForReference);
}
});
return result == ANDROID_PLATFORM ? null : result;
}
}