blob: 300eed5e7b0a608ab7b9201fc8d72734d3e4be5c [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.apimodel;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.androidapi.AndroidApiLevelHashingDatabaseImpl;
import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMember;
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.references.ClassReference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.Pair;
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 java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
public class AndroidApiHashingDatabaseBuilderGenerator extends TestBase {
/**
* Generate the information needed for looking up api level of references in the android.jar. This
* method will generate three different files and store them in the passed paths. pathToIndices
* will be an int array with hashcode entries for each DexReference. The pathToApiLevels is a byte
* array with a byte describing the api level that the index in pathToIndices has. To ensure that
* this lookup work the generate algorithm tracks all colliding hash codes such and stores them in
* another format. The indices map is populated with all colliding entries and a -1 is inserted
* for the api level.
*/
public static void generate(
List<ParsedApiClass> apiClasses,
Path pathToIndices,
Path pathToApiLevels,
Path ambiguousDefinitions)
throws Exception {
Map<ClassReference, Map<DexMethod, AndroidApiLevel>> methodMap = new HashMap<>();
Map<ClassReference, Map<DexField, AndroidApiLevel>> fieldMap = new HashMap<>();
Map<ClassReference, ParsedApiClass> lookupMap = new HashMap<>();
DexItemFactory factory = new DexItemFactory();
Map<Integer, AndroidApiLevel> apiLevelMap = new HashMap<>();
Map<Integer, Pair<DexReference, AndroidApiLevel>> reverseMap = new HashMap<>();
Map<AndroidApiLevel, Set<DexReference>> ambiguousMap = new HashMap<>();
// Populate maps for faster lookup.
for (ParsedApiClass apiClass : apiClasses) {
DexType type = factory.createType(apiClass.getClassReference().getDescriptor());
AndroidApiLevel existing = apiLevelMap.put(type.hashCode(), apiClass.getApiLevel());
assert existing == null;
reverseMap.put(type.hashCode(), Pair.create(type, apiClass.getApiLevel()));
}
for (ParsedApiClass apiClass : apiClasses) {
Map<DexMethod, AndroidApiLevel> methodsForApiClass = new HashMap<>();
apiClass.visitMethodReferences(
(apiLevel, methods) -> {
methods.forEach(
method -> methodsForApiClass.put(factory.createMethod(method), apiLevel));
});
Map<DexField, AndroidApiLevel> fieldsForApiClass = new HashMap<>();
apiClass.visitFieldReferences(
(apiLevel, fields) -> {
fields.forEach(field -> fieldsForApiClass.put(factory.createField(field), apiLevel));
});
methodMap.put(apiClass.getClassReference(), methodsForApiClass);
fieldMap.put(apiClass.getClassReference(), fieldsForApiClass);
lookupMap.put(apiClass.getClassReference(), apiClass);
}
BiConsumer<DexReference, AndroidApiLevel> addConsumer =
addReferenceToMaps(apiLevelMap, reverseMap, ambiguousMap);
for (ParsedApiClass apiClass : apiClasses) {
computeAllReferencesInHierarchy(
lookupMap,
factory,
factory.createType(apiClass.getClassReference().getDescriptor()),
apiClass,
AndroidApiLevel.B,
new IdentityHashMap<>())
.forEach(addConsumer);
}
int[] indices = new int[apiLevelMap.size()];
byte[] apiLevel = new byte[apiLevelMap.size()];
ArrayList<Integer> integers = new ArrayList<>(apiLevelMap.keySet());
for (int i = 0; i < integers.size(); i++) {
indices[i] = integers.get(i);
AndroidApiLevel androidApiLevel = apiLevelMap.get(integers.get(i));
apiLevel[i] = (byte) (androidApiLevel == null ? -1 : androidApiLevel.getLevel());
}
try (FileOutputStream fileOutputStream = new FileOutputStream(pathToIndices.toFile());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
objectOutputStream.writeObject(indices);
}
try (FileOutputStream fileOutputStream = new FileOutputStream(pathToApiLevels.toFile());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
objectOutputStream.writeObject(apiLevel);
}
String ambiguousMapSerialized = serializeAmbiguousMap(ambiguousMap);
Files.write(ambiguousDefinitions, ambiguousMapSerialized.getBytes(StandardCharsets.UTF_8));
}
/** This will serialize a collection of DexReferences with the api level they correspond to. */
private static String serializeAmbiguousMap(
Map<AndroidApiLevel, Set<DexReference>> ambiguousMap) {
Set<String> seen = new HashSet<>();
StringBuilder sb = new StringBuilder();
ambiguousMap.forEach(
(apiLevel, references) -> {
references.forEach(
reference -> {
HasherWrapper defaultHasher = AndroidApiLevelHashingDatabaseImpl.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 referenceHash = defaultHasher.hash().toString();
if (!seen.add(referenceHash)) {
throw new RuntimeException(
"More than one item with key: <"
+ referenceHash
+ ">. The choice of key encoding will need to change to generate a valid"
+ " api database");
}
sb.append(referenceHash).append(":").append(apiLevel.getLevel()).append("\n");
});
});
return sb.toString();
}
private static BiConsumer<DexReference, AndroidApiLevel> addReferenceToMaps(
Map<Integer, AndroidApiLevel> apiLevelMap,
Map<Integer, Pair<DexReference, AndroidApiLevel>> reverseMap,
Map<AndroidApiLevel, Set<DexReference>> ambiguousMap) {
return ((reference, apiLevel) -> {
AndroidApiLevel existingMethod = apiLevelMap.put(reference.hashCode(), apiLevel);
if (existingMethod != null) {
apiLevelMap.put(reference.hashCode(), null);
Pair<DexReference, AndroidApiLevel> existingPair = reverseMap.get(reference.hashCode());
addAmbiguousEntry(existingPair.getSecond(), existingPair.getFirst(), ambiguousMap);
addAmbiguousEntry(apiLevel, reference, ambiguousMap);
} else {
reverseMap.put(reference.hashCode(), Pair.create(reference, apiLevel));
}
});
}
private static void addAmbiguousEntry(
AndroidApiLevel apiLevel,
DexReference reference,
Map<AndroidApiLevel, Set<DexReference>> ambiguousMap) {
ambiguousMap.computeIfAbsent(apiLevel, ignored -> new HashSet<>()).add(reference);
}
@SuppressWarnings("unchecked")
private static <T extends DexMember<?, ?>>
Map<T, AndroidApiLevel> computeAllReferencesInHierarchy(
Map<ClassReference, ParsedApiClass> lookupMap,
DexItemFactory factory,
DexType holder,
ParsedApiClass apiClass,
AndroidApiLevel linkLevel,
Map<T, AndroidApiLevel> additionMap) {
if (!apiClass.getClassReference().getDescriptor().equals(factory.objectDescriptor.toString())) {
apiClass.visitMethodReferences(
(apiLevel, methodReferences) -> {
methodReferences.forEach(
methodReference -> {
T member = (T) factory.createMethod(methodReference).withHolder(holder, factory);
addIfNewOrApiLevelIsLower(linkLevel, additionMap, apiLevel, member);
});
});
apiClass.visitFieldReferences(
(apiLevel, fieldReferences) -> {
fieldReferences.forEach(
fieldReference -> {
T member = (T) factory.createField(fieldReference).withHolder(holder, factory);
addIfNewOrApiLevelIsLower(linkLevel, additionMap, apiLevel, member);
});
});
apiClass.visitSuperType(
(superType, apiLevel) -> {
computeAllReferencesInHierarchy(
lookupMap,
factory,
holder,
lookupMap.get(superType),
linkLevel.max(apiLevel),
additionMap);
});
apiClass.visitInterface(
(iFace, apiLevel) -> {
computeAllReferencesInHierarchy(
lookupMap,
factory,
holder,
lookupMap.get(iFace),
linkLevel.max(apiLevel),
additionMap);
});
}
return additionMap;
}
private static <T extends DexMember<?, ?>> void addIfNewOrApiLevelIsLower(
AndroidApiLevel linkLevel,
Map<T, AndroidApiLevel> additionMap,
AndroidApiLevel apiLevel,
T member) {
AndroidApiLevel currentApiLevel = apiLevel.max(linkLevel);
AndroidApiLevel existingApiLevel = additionMap.get(member);
if (existingApiLevel == null || currentApiLevel.isLessThanOrEqualTo(existingApiLevel)) {
additionMap.put(member, currentApiLevel);
}
}
}