blob: dc870477e4545231b47e2452e281507e45db4fb0 [file] [log] [blame]
// Copyright (c) 2024, 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.shaking;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ObjectUtils;
import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class KeepInfoCollectionExported {
static final String CLASS_INFO = "class info";
static final String FIELD = "field";
static final String METHOD = "method";
private final Map<TypeReference, ExportedClassInfo> classInfos;
public static KeepInfoCollectionExported parse(Path folder) throws IOException {
Map<TypeReference, ExportedClassInfo> result = new IdentityHashMap<>();
parseKeepInfo(folder, folder, result);
return new KeepInfoCollectionExported(ImmutableMap.copyOf(result));
}
private static void parseKeepInfo(
Path root, Path dir, Map<TypeReference, ExportedClassInfo> result) throws IOException {
File file = dir.toFile();
if (file.exists()) {
File[] files = file.listFiles();
if (files != null) {
for (File child : files) {
Path childPath = child.toPath();
if (child.isDirectory()) {
parseKeepInfo(root, childPath, result);
} else {
Path relative = root.relativize(childPath);
result.put(
Reference.typeFromDescriptor("L" + relative + ";"),
ExportedClassInfo.parse(childPath));
}
}
}
}
}
static class ExportedClassInfo {
private final KeepClassInfo keepClassInfo;
private final Map<FieldReference, KeepFieldInfo> fieldInfos;
private final Map<MethodReference, KeepMethodInfo> methodInfos;
ExportedClassInfo(
KeepClassInfo keepClassInfo,
Map<FieldReference, KeepFieldInfo> fieldInfos,
Map<MethodReference, KeepMethodInfo> methodInfos) {
this.keepClassInfo = keepClassInfo;
this.fieldInfos = fieldInfos;
this.methodInfos = methodInfos;
}
public boolean isEqualsTo(ExportedClassInfo other) {
if (!fieldInfos.keySet().equals(other.fieldInfos.keySet())) {
return false;
}
for (FieldReference fieldReference : fieldInfos.keySet()) {
if (!fieldInfos
.get(fieldReference)
.equalsWithAnnotations(other.fieldInfos.get(fieldReference))) {
return false;
}
}
if (!methodInfos.keySet().equals(other.methodInfos.keySet())) {
return false;
}
for (MethodReference methodReference : methodInfos.keySet()) {
if (!methodInfos
.get(methodReference)
.equalsWithAnnotations(other.methodInfos.get(methodReference))) {
return false;
}
}
if (keepClassInfo == null) {
return other.keepClassInfo == null;
}
return keepClassInfo.equalsWithAnnotations(other.keepClassInfo);
}
@Override
public int hashCode() {
return ObjectUtils.hashLLL(keepClassInfo, fieldInfos, methodInfos);
}
public List<String> lines() {
List<String> lines = new ArrayList<>();
if (keepClassInfo != null) {
lines.add(CLASS_INFO);
lines.addAll(keepClassInfo.lines());
lines.add("");
}
List<FieldReference> fieldRefs = new ArrayList<>(fieldInfos.keySet());
fieldRefs.sort(Comparator.comparing(FieldReference::toSourceString));
for (FieldReference fieldRef : fieldRefs) {
lines.add(FIELD + " " + fieldRef.toSourceString());
lines.addAll(fieldInfos.get(fieldRef).lines());
lines.add("");
}
List<MethodReference> methodRefs = new ArrayList<>(methodInfos.keySet());
methodRefs.sort(Comparator.comparing(MethodReference::toSourceString));
for (MethodReference methodRef : methodRefs) {
lines.add(METHOD + " " + methodRef.toSourceString());
lines.addAll(methodInfos.get(methodRef).lines());
lines.add("");
}
return lines;
}
public static ExportedClassInfo parse(Path path) throws IOException {
List<String> lines = Files.readAllLines(path);
Builder builder = new Builder();
Iterator<String> iterator = lines.iterator();
while (iterator.hasNext()) {
String line = iterator.next();
while (line.isEmpty() && iterator.hasNext()) {
line = iterator.next();
}
if (line.equals(CLASS_INFO)) {
builder.setKeepClassInfo(KeepClassInfo.parse(iterator));
}
if (line.startsWith(FIELD)) {
FieldReference fieldReference = readFieldReference(line.substring(FIELD.length() + 1));
builder.putFieldInfo(fieldReference, KeepFieldInfo.parse(iterator));
}
if (line.startsWith(METHOD)) {
MethodReference methodReference =
readMethodReference(line.substring(METHOD.length() + 1));
builder.putMethodInfo(methodReference, KeepMethodInfo.parse(iterator));
}
}
return builder.build();
}
private static MethodReference readMethodReference(String substring) {
int first = substring.indexOf(" ");
String returnString = substring.substring(0, first);
TypeReference returnType =
returnString.equals("void") ? null : Reference.typeFromTypeName(returnString);
int brace = substring.indexOf("(");
String holderAndName = substring.substring(first + 1, brace);
int nameIndex = holderAndName.lastIndexOf(".");
ClassReference holder = Reference.classFromTypeName(holderAndName.substring(0, nameIndex));
String methodName = holderAndName.substring(nameIndex + 1);
String[] argStrings = substring.substring(brace + 1, substring.length() - 1).split(", ");
TypeReference[] args =
argStrings.length == 1 && argStrings[0].isEmpty()
? new TypeReference[0]
: ArrayUtils.map(argStrings, Reference::typeFromTypeName, new TypeReference[0]);
return Reference.method(holder, methodName, Arrays.asList(args), returnType);
}
private static FieldReference readFieldReference(String string) {
int first = string.indexOf(" ");
int last = string.lastIndexOf(".");
TypeReference fieldType = Reference.typeFromTypeName(string.substring(0, first));
ClassReference classType = Reference.classFromTypeName(string.substring(first + 1, last));
String fieldName = string.substring(last + 1);
return Reference.field(classType, fieldName, fieldType);
}
static Builder builder() {
return new Builder();
}
static class Builder {
private KeepClassInfo keepClassInfo;
private final Map<FieldReference, KeepFieldInfo> fieldInfos = new HashMap<>();
private final Map<MethodReference, KeepMethodInfo> methodInfos = new HashMap<>();
public void setKeepClassInfo(KeepClassInfo keepClassInfo) {
this.keepClassInfo = keepClassInfo;
}
public void putFieldInfo(FieldReference fieldReference, KeepFieldInfo keepFieldInfo) {
fieldInfos.put(fieldReference, keepFieldInfo);
}
public void putMethodInfo(MethodReference methodReference, KeepMethodInfo keepMethodInfo) {
methodInfos.put(methodReference, keepMethodInfo);
}
public ExportedClassInfo build() {
return new ExportedClassInfo(
keepClassInfo, ImmutableMap.copyOf(fieldInfos), ImmutableMap.copyOf(methodInfos));
}
}
}
public KeepInfoCollectionExported(Map<TypeReference, ExportedClassInfo> classInfos) {
this.classInfos = classInfos;
}
public KeepInfoCollectionExported(
Map<DexType, KeepClassInfo> keepClassInfo,
Map<DexMethod, KeepMethodInfo> keepMethodInfo,
Map<DexField, KeepFieldInfo> keepFieldInfo) {
Map<TypeReference, ExportedClassInfo.Builder> classInfosBuilder = new HashMap<>();
keepClassInfo.forEach(
(type, info) -> {
ExportedClassInfo.Builder builder = getBuilder(classInfosBuilder, type.asTypeReference());
builder.setKeepClassInfo(export(info));
});
keepMethodInfo.forEach(
(method, info) -> {
ExportedClassInfo.Builder builder =
getBuilder(classInfosBuilder, method.getHolderType().asTypeReference());
builder.putMethodInfo(method.asMethodReference(), export(info));
});
keepFieldInfo.forEach(
(field, info) -> {
ExportedClassInfo.Builder builder =
getBuilder(classInfosBuilder, field.getHolderType().asTypeReference());
builder.putFieldInfo(field.asFieldReference(), export(info));
});
ImmutableMap.Builder<TypeReference, ExportedClassInfo> mapBuilder = ImmutableMap.builder();
classInfosBuilder.forEach((typeRef, builder) -> mapBuilder.put(typeRef, builder.build()));
classInfos = mapBuilder.build();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof KeepInfoCollectionExported)) {
return false;
}
KeepInfoCollectionExported other = (KeepInfoCollectionExported) obj;
if (!classInfos.keySet().equals(other.classInfos.keySet())) {
return false;
}
for (TypeReference typeReference : classInfos.keySet()) {
if (!classInfos.get(typeReference).isEqualsTo(other.classInfos.get(typeReference))) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return classInfos.hashCode();
}
public KeepClassInfo getKeepClassInfo(TypeReference typeReference) {
ExportedClassInfo info = classInfos.get(typeReference);
return info == null ? null : info.keepClassInfo;
}
public KeepMethodInfo getKeepMethodInfo(MethodReference methodReference) {
if (!classInfos.containsKey(methodReference.getHolderClass())) {
return null;
}
return classInfos.get(methodReference.getHolderClass()).methodInfos.get(methodReference);
}
public KeepFieldInfo getKeepFieldInfo(FieldReference fieldReference) {
if (!classInfos.containsKey(fieldReference.getHolderClass())) {
return null;
}
return classInfos.get(fieldReference.getHolderClass()).fieldInfos.get(fieldReference);
}
private ExportedClassInfo.Builder getBuilder(
Map<TypeReference, ExportedClassInfo.Builder> classInfosBuilder,
TypeReference typeReference) {
return classInfosBuilder.computeIfAbsent(typeReference, t -> ExportedClassInfo.builder());
}
public void exportToDirectory(Path directory) throws IOException {
for (Entry<TypeReference, ExportedClassInfo> entry : classInfos.entrySet()) {
TypeReference typeReference = entry.getKey();
ExportedClassInfo exportedClassInfo = entry.getValue();
String binaryName =
DescriptorUtils.getClassBinaryNameFromDescriptor(typeReference.getDescriptor());
Files.createDirectories(directory.resolve(binaryName).getParent());
Files.write(
directory.resolve(binaryName), exportedClassInfo.lines(), StandardOpenOption.CREATE);
}
}
static boolean noAnnotation(KeepInfo<?, ?> info) {
return info.internalAnnotationsInfo().isTopOrBottom()
&& info.internalTypeAnnotationsInfo().isTopOrBottom();
}
private KeepClassInfo export(KeepClassInfo classInfo) {
if (noAnnotation(classInfo)) {
return classInfo;
}
KeepClassInfo.Builder builder = classInfo.builder();
builder.getAnnotationsInfo().setExport();
builder.getTypeAnnotationsInfo().setExport();
return builder.build();
}
private KeepFieldInfo export(KeepFieldInfo fieldInfo) {
if (noAnnotation(fieldInfo)) {
return fieldInfo;
}
KeepFieldInfo.Builder builder = fieldInfo.builder();
builder.getAnnotationsInfo().setExport();
builder.getTypeAnnotationsInfo().setExport();
return builder.build();
}
private KeepMethodInfo export(KeepMethodInfo methodInfo) {
if (noAnnotation(methodInfo) && methodInfo.internalParameterAnnotationsInfo().isTopOrBottom()) {
return methodInfo;
}
KeepMethodInfo.Builder builder = methodInfo.builder();
builder.getAnnotationsInfo().setExport();
builder.getTypeAnnotationsInfo().setExport();
builder.getParameterAnnotationsInfo().setExport();
return builder.build();
}
public static class KeepAnnotationCollectionInfoExported extends KeepAnnotationCollectionInfo {
static KeepAnnotationCollectionInfo createExported(KeepAnnotationCollectionInfo from) {
if (from.isTopOrBottom()) {
return from;
}
return new KeepAnnotationCollectionInfoExported(from.toString());
}
private final String export;
KeepAnnotationCollectionInfoExported(String export) {
this.export = export;
}
public static KeepAnnotationCollectionInfo.Builder parse(String value) {
if (value.equals("top")) {
return Builder.createTop();
}
if (value.equals("bottom")) {
return Builder.createBottom();
}
return new Builder(KeepAnnotationInfo.getTop()) {
@Override
public KeepAnnotationCollectionInfo build() {
return new KeepAnnotationCollectionInfoExported(value);
}
};
}
@Override
public String toString() {
return export;
}
@Override
public boolean isRemovalAllowed(DexAnnotation annotation) {
throw new Unreachable();
}
}
}