blob: f87ec27d427bc1471a8b208b76c4f8d5f1644e66 [file] [log] [blame]
// Copyright (c) 2020, 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.cfmethodgeneration;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.cf.CfCodePrinter;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.graph.JarClassFileReader;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
import com.beust.jcommander.internal.Sets;
import com.google.common.collect.ImmutableList;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
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.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public abstract class MethodGenerationBase extends CodeGenerationBase {
protected abstract List<Class<?>> getMethodTemplateClasses();
protected List<Class<?>> getClassesToGenerate() {
return ImmutableList.of();
}
protected DexEncodedField getField(DexEncodedField field) {
return field;
}
protected boolean includeMethod(DexEncodedMethod method) {
return !method.isInstanceInitializer();
}
protected CfCode getCode(String holderName, String methodName, CfCode code) {
return code;
}
protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
return method;
}
// Running this method will regenerate / overwrite the content of the generated class.
protected void generateMethodsAndWriteThemToFile() throws IOException {
FileUtils.writeToFile(getGeneratedFile(), null, generateMethods().getBytes());
}
// Running this method generate the content of the generated class but does not overwrite it.
protected String generateMethods() throws IOException {
CfCodePrinter codePrinter = new CfCodePrinter();
File tempFile = File.createTempFile("output-", ".java");
Map<DexEncodedMethod, String> generatedMethods = new HashMap<>();
List<DexEncodedField> fields = new ArrayList<>();
readMethodTemplatesInto(codePrinter, generatedMethods::put, fields::add);
generateRawOutput(generatedMethods, fields, codePrinter, tempFile.toPath());
String result = formatRawOutput(tempFile.toPath());
tempFile.deleteOnExit();
return result;
}
private void readMethodTemplatesInto(
CfCodePrinter codePrinter,
BiConsumer<DexEncodedMethod, String> generatedMethods,
Consumer<DexEncodedField> generatedFields)
throws IOException {
InternalOptions options = new InternalOptions(factory, new Reporter());
options.testing.readInputStackMaps = true;
JarClassFileReader<DexProgramClass> reader =
new JarClassFileReader<>(
new JarApplicationReader(options),
clazz -> {
for (DexEncodedField field : clazz.fields()) {
DexEncodedField fieldToAddToClass = getField(field);
if (fieldToAddToClass != null) {
generatedFields.accept(fieldToAddToClass);
}
}
for (DexEncodedMethod method : clazz.allMethodsSorted()) {
if (!includeMethod(method)) {
continue;
}
String holderName = method.getHolderType().getName();
String methodName = method.getReference().name.toString();
if (methodName.equals("<init>")) {
methodName = "constructor_" + method.getProto().getArity();
}
String generatedMethodName = holderName + "_" + methodName;
CfCode code = getCode(holderName, methodName, method.getCode().asCfCode());
if (code != null) {
codePrinter.visitMethod(generatedMethodName, code);
generatedMethods.accept(method, generatedMethodName);
}
}
},
ClassKind.PROGRAM);
for (Class<?> clazz : getMethodTemplateClasses()) {
reader.read(Origin.unknown(), ToolHelper.getClassAsBytes(clazz));
}
}
private String createType(DexType type) {
if (type.isVoidType()) {
return "factory.voidType";
}
if (type.isBooleanType()) {
return "factory.booleanType";
}
if (type.isIntType()) {
return "factory.intType";
}
if (type.isLongType()) {
return "factory.longType";
}
if (type.descriptor.toString().equals("Ljava/lang/Object;")) {
return "factory.objectType";
}
return "factory.createType(factory.createString(\"" + type.getDescriptor() + "\"))";
}
private String createField(DexEncodedField field) {
return "factory.createField(\n"
+ "builder.getType(),\n"
+ createType(field.getType())
+ ",\n"
+ "factory.createString(\""
+ field.getName()
+ "\"))\n";
}
private String buildSyntheticMethod(
DexEncodedMethod method, String codeGenerator, Set<String> requiredImports) {
String name =
method.isInstanceInitializer()
? "constructor_" + method.getProto().getArity()
: method.getName().toString();
requiredImports.add("com.android.tools.r8.graph.DexEncodedMethod");
requiredImports.add("com.android.tools.r8.graph.MethodAccessFlags");
requiredImports.add("com.android.tools.r8.dex.Constants");
return "DexEncodedMethod.syntheticBuilder()\n"
+ " .setMethod("
+ name
+ ")\n"
+ " .setAccessFlags(\n"
+ " MethodAccessFlags.fromSharedAccessFlags(\n"
+ " Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, "
+ (method.isInstanceInitializer())
+ "))\n"
+ " .setCode("
+ codeGenerator
+ "(factory, "
+ name
+ "))\n"
+ " .disableAndroidApiLevelCheck()\n"
+ " .build()";
}
private String generateCreateProto(DexProto proto) {
StringBuilder builder = new StringBuilder("factory.createProto(");
builder.append(createType(proto.returnType));
proto.getParameters().forEach(type -> builder.append(", ").append(createType(type)));
builder.append(")");
return builder.toString();
}
private String generateCreateMethod(DexEncodedMethod method) {
return "factory.createMethod(\n"
+ "builder.getType(), "
+ generateCreateProto(method.getProto())
+ ", factory.createString(\""
+ method.getName()
+ "\"));";
}
private void generateDexMethodLocals(
Map<DexEncodedMethod, String> generatedMethods, PrintStream printer, Class<?> clazz) {
// Generate local variable for a DexMethod:
// DexMethod name = factory.createMethod(
// builder.getType(), factory.createProto(...), factory.createString(...));
generatedMethods.forEach(
(method, codeGenerator) -> {
if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
String name =
method.isInstanceInitializer()
? "constructor_" + method.getProto().getArity()
: method.getName().toString();
printer.println("DexMethod " + name + " = " + generateCreateMethod(mapMethod(method)));
}
});
}
private void generateSyntheticMethodsList(
Map<DexEncodedMethod, String> generatedMethods,
Set<String> requiredImports,
PrintStream printer,
Class<?> clazz,
Predicate<DexEncodedMethod> filter) {
printer.println("ImmutableList.of(");
BooleanBox first = new BooleanBox(true);
generatedMethods.forEach(
(method, codeGenerator) -> {
if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())
&& filter.test(method)) {
if (!first.get()) {
printer.println(",\n");
}
first.set(false);
printer.println(buildSyntheticMethod(method, codeGenerator, requiredImports));
}
});
printer.println(")");
}
private String buildSyntheticField(DexEncodedField field, Set<String> requiredImports) {
requiredImports.add("com.android.tools.r8.graph.DexEncodedField");
requiredImports.add("com.android.tools.r8.graph.FieldAccessFlags");
return " DexEncodedField.syntheticBuilder()\n"
+ " .setField("
+ createField(field)
+ ")\n"
+ " .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())\n"
+ " .disableAndroidApiLevelCheck()\n"
+ " .build()\n";
}
private void generateFieldList(
List<DexEncodedField> fields,
Set<String> requiredImports,
PrintStream printer,
Class<?> clazz) {
printer.println("ImmutableList.of(");
BooleanBox first = new BooleanBox(true);
for (DexEncodedField field : fields) {
if (field.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
if (!first.get()) {
printer.println(",\n");
}
first.set(false);
printer.println(buildSyntheticField(field, requiredImports));
}
}
printer.println(")");
}
private <K, V> Map<K, V> filterMapOnKey(Map<K, V> map, Predicate<K> predicate) {
return map.entrySet().stream()
.filter(entry -> predicate.test(entry.getKey()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
private void generateGenerateClasses(
Map<DexEncodedMethod, String> allMethods,
List<DexEncodedField> allFields,
Set<String> requiredImports,
PrintStream printer) {
for (Class<?> clazz : getClassesToGenerate()) {
String simpleName =
clazz.getSimpleName(); // "DesugarVarHandle"; //name.substring(name.lastIndexOf('.'));
List<DexEncodedField> classFields =
ListUtils.filter(
allFields,
field -> field.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
Map<DexEncodedMethod, String> classMethods =
filterMapOnKey(
allMethods,
method -> method.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
requiredImports.add("com.android.tools.r8.synthesis.SyntheticProgramClassBuilder");
printer.println(
"public static void generate"
+ simpleName
+ "Class(SyntheticProgramClassBuilder builder, DexItemFactory factory) {");
printer.println("builder.setInstanceFields(");
generateFieldList(classFields, requiredImports, printer, clazz);
printer.println(");");
generateDexMethodLocals(classMethods, printer, clazz);
if (clazz.getSuperclass() != Object.class) {
printer.println(
" builder.setSuperType("
+ createType(factory.createType(Reference.classFromClass(clazz.getSuperclass())))
+ ");");
}
printer.println("builder.setDirectMethods(");
generateSyntheticMethodsList(
classMethods, requiredImports, printer, clazz, DexEncodedMethod::isInstanceInitializer);
printer.println(");");
printer.println("builder.setVirtualMethods(");
generateSyntheticMethodsList(
allMethods, requiredImports, printer, clazz, method -> !method.isInstanceInitializer());
printer.println(");");
printer.println("}");
}
}
private void generateRawOutput(
Map<DexEncodedMethod, String> generatedMethods,
List<DexEncodedField> fields,
CfCodePrinter codePrinter,
Path tempFile)
throws IOException {
try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
printer.print(getHeaderString());
Set<String> imports = Sets.newHashSet();
imports.add("com.android.tools.r8.graph.DexItemFactory");
// TODO(b/260985726): Consider only calling generateGenerateClasses once.
// Generate classes into an unused PrintStream to collect imports.
generateGenerateClasses(
generatedMethods,
fields,
imports,
new PrintStream(new ByteArrayOutputStream(), true, StandardCharsets.UTF_8.name()));
imports.addAll(codePrinter.getImports());
ArrayList<String> sortedImports = new ArrayList<>(imports);
sortedImports.sort(String::compareTo);
sortedImports.forEach(i -> printer.println("import " + i + ";"));
printer.println("public final class " + getGeneratedClassName() + " {\n");
printer.println(
"public static void registerSynthesizedCodeReferences(DexItemFactory factory) {");
for (String type : new TreeSet<>(codePrinter.getSynthesizedTypes())) {
printer.println("factory.createSynthesizedType(\"" + type + "\");");
}
printer.println("}");
// TODO(b/260985726): Consider only calling generateGenerateClasses once.
// Generate classes ignoring the collected imports (they are already added to the file).
generateGenerateClasses(generatedMethods, fields, Sets.newHashSet(), printer);
codePrinter.getMethods().forEach(printer::println);
printer.println("}");
}
}
}