blob: f5343da91429b126ede99fa471664646396845b2 [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.utils.AndroidApiLevel.ANDROID_PLATFORM;
import com.android.tools.r8.errors.CompilationError;
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.DexType;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.structural.DefaultHashingVisitor;
import com.android.tools.r8.utils.structural.HasherWrapper;
import com.android.tools.r8.utils.structural.StructuralItem;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class AndroidApiLevelHashingDatabaseImpl implements AndroidApiLevelDatabase {
public static HasherWrapper getDefaultHasher() {
return HasherWrapper.murmur3128Hasher();
}
private final Int2ReferenceMap<AndroidApiLevel> lookupNonAmbiguousCache =
new Int2ReferenceOpenHashMap<AndroidApiLevel>();
private final Map<String, AndroidApiLevel> ambiguousHashesWithApiLevel = new HashMap<>();
private final Map<DexReference, AndroidApiLevel> ambiguousCache = new IdentityHashMap<>();
public AndroidApiLevelHashingDatabaseImpl(
List<AndroidApiForHashingReference> predefinedApiTypeLookup) {
loadData();
predefinedApiTypeLookup.forEach(
predefinedApiReference -> {
int hashCode = predefinedApiReference.getReference().hashCode();
// Do not use computeIfAbsent since a return value of null implies the key should not be
// inserted.
if (!lookupNonAmbiguousCache.containsKey(hashCode)) {
lookupNonAmbiguousCache.put(hashCode, null);
ambiguousCache.put(
predefinedApiReference.getReference(), predefinedApiReference.getApiLevel());
}
});
assert predefinedApiTypeLookup.stream()
.allMatch(added -> added.getApiLevel().isEqualTo(lookupApiLevel(added.getReference())));
}
private void loadData() {
int[] hashIndices;
byte[] apiLevels;
List<String> ambiguous;
try (InputStream indicesInputStream =
getClass()
.getClassLoader()
.getResourceAsStream("api_database/api_database_hash_lookup.ser");
ObjectInputStream indicesObjectStream = new ObjectInputStream(indicesInputStream);
InputStream apiInputStream =
getClass()
.getClassLoader()
.getResourceAsStream("api_database/api_database_api_level.ser");
ObjectInputStream apiObjectStream = new ObjectInputStream(apiInputStream);
InputStream ambiguousInputStream =
getClass()
.getClassLoader()
.getResourceAsStream("api_database/api_database_ambiguous.txt")) {
hashIndices = (int[]) indicesObjectStream.readObject();
apiLevels = (byte[]) apiObjectStream.readObject();
ambiguous =
new BufferedReader(new InputStreamReader(ambiguousInputStream, StandardCharsets.UTF_8))
.lines()
.collect(Collectors.toList());
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Could not build api database");
}
assert hashIndices.length == apiLevels.length;
for (int i = 0; i < hashIndices.length; i++) {
byte apiLevel = apiLevels[i];
lookupNonAmbiguousCache.put(
hashIndices[i], apiLevel == -1 ? null : AndroidApiLevel.getAndroidApiLevel(apiLevel));
}
ambiguous.forEach(this::parseAmbiguous);
}
/**
* All elements in the ambiguous map are on the form <key>:<api-level>. The reason for this
* additional map is that the keys collide for the items using the ordinary hashing function.
*/
private void parseAmbiguous(String ambiguous) {
String[] split = ambiguous.split(":");
if (split.length != 2) {
throw new CompilationError("Expected two entries in ambiguous map");
}
ambiguousHashesWithApiLevel.put(
split[0], AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(split[1])));
}
@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 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 =
lookupNonAmbiguousCache.getOrDefault(reference.hashCode(), ANDROID_PLATFORM);
if (result != null) {
return result == ANDROID_PLATFORM ? null : result;
}
return ambiguousCache.computeIfAbsent(
reference,
ignored -> {
HasherWrapper defaultHasher = getDefaultHasher();
reference.accept(
type -> DefaultHashingVisitor.run(type, defaultHasher, DexType::acceptHashing),
field ->
DefaultHashingVisitor.run(field, defaultHasher, StructuralItem::acceptHashing),
method ->
DefaultHashingVisitor.run(method, defaultHasher, StructuralItem::acceptHashing));
String existingHash = defaultHasher.hash().toString();
AndroidApiLevel androidApiLevel = ambiguousHashesWithApiLevel.get(existingHash);
if (androidApiLevel == null) {
throw new CompilationError(
"Failed to find api level for reference: "
+ reference.toSourceString()
+ " with hash value: "
+ existingHash);
}
return androidApiLevel;
});
}
}