| // 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.TestBase; | |
| import com.android.tools.r8.ToolHelper; | |
| import com.android.tools.r8.ToolHelper.ProcessResult; | |
| 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.DexEncodedMethod; | |
| import com.android.tools.r8.graph.DexItemFactory; | |
| import com.android.tools.r8.graph.DexProgramClass; | |
| 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.utils.FileUtils; | |
| import com.android.tools.r8.utils.InternalOptions; | |
| import com.android.tools.r8.utils.Reporter; | |
| import com.android.tools.r8.utils.StringUtils; | |
| import com.google.common.collect.ImmutableList; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.io.PrintStream; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.util.List; | |
| import java.util.TreeSet; | |
| public abstract class MethodGenerationBase extends TestBase { | |
| private static final Path GOOGLE_FORMAT_DIR = | |
| Paths.get(ToolHelper.THIRD_PARTY_DIR, "google-java-format"); | |
| private static final Path GOOGLE_FORMAT_JAR = | |
| GOOGLE_FORMAT_DIR.resolve("google-java-format-1.7-all-deps.jar"); | |
| protected final DexItemFactory factory = new DexItemFactory(); | |
| protected static String getJavaExecutable() { | |
| return ToolHelper.getSystemJavaExecutable(); | |
| } | |
| protected abstract DexType getGeneratedType(); | |
| protected abstract List<Class<?>> getMethodTemplateClasses(); | |
| protected abstract int getYear(); | |
| public String getHeaderString() { | |
| String simpleName = getClass().getSimpleName(); | |
| return StringUtils.lines( | |
| "// Copyright (c) " + getYear() + ", 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.", | |
| "", | |
| "// ***********************************************************************************", | |
| "// GENERATED FILE. DO NOT EDIT! See " + simpleName + ".java.", | |
| "// ***********************************************************************************", | |
| "", | |
| "package " + getGeneratedClassPackageName() + ";"); | |
| } | |
| protected Path getGeneratedFile() { | |
| return Paths.get(ToolHelper.SOURCE_DIR, getGeneratedType().getInternalName() + ".java"); | |
| } | |
| protected CfCode getCode(String holderName, String methodName, CfCode code) { | |
| return code; | |
| } | |
| private String getGeneratedClassName() { | |
| return getGeneratedType().getName(); | |
| } | |
| private String getGeneratedClassPackageName() { | |
| return getGeneratedType().getPackageName(); | |
| } | |
| // 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"); | |
| readMethodTemplatesInto(codePrinter); | |
| generateRawOutput(codePrinter, tempFile.toPath()); | |
| String result = formatRawOutput(tempFile.toPath()); | |
| tempFile.deleteOnExit(); | |
| return result; | |
| } | |
| private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException { | |
| InternalOptions options = new InternalOptions(factory, new Reporter()); | |
| options.testing.readInputStackMaps = true; | |
| JarClassFileReader<DexProgramClass> reader = | |
| new JarClassFileReader<>( | |
| new JarApplicationReader(options), | |
| clazz -> { | |
| for (DexEncodedMethod method : clazz.allMethodsSorted()) { | |
| if (method.isInitializer()) { | |
| continue; | |
| } | |
| String holderName = method.getHolderType().getName(); | |
| String methodName = method.getReference().name.toString(); | |
| String generatedMethodName = holderName + "_" + methodName; | |
| CfCode code = getCode(holderName, methodName, method.getCode().asCfCode()); | |
| if (code != null) { | |
| codePrinter.visitMethod(generatedMethodName, code); | |
| } | |
| } | |
| }, | |
| ClassKind.PROGRAM); | |
| for (Class<?> clazz : getMethodTemplateClasses()) { | |
| reader.read(Origin.unknown(), ToolHelper.getClassAsBytes(clazz)); | |
| } | |
| } | |
| private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException { | |
| try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) { | |
| printer.print(getHeaderString()); | |
| printer.println("import com.android.tools.r8.graph.DexItemFactory;"); | |
| codePrinter.getImports().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("}"); | |
| codePrinter.getMethods().forEach(printer::println); | |
| printer.println("}"); | |
| } | |
| } | |
| public static String formatRawOutput(Path tempFile) throws IOException { | |
| // Apply google format. | |
| ProcessBuilder builder = | |
| new ProcessBuilder( | |
| ImmutableList.of( | |
| getJavaExecutable(), | |
| "-jar", | |
| GOOGLE_FORMAT_JAR.toString(), | |
| tempFile.toAbsolutePath().toString())); | |
| String commandString = String.join(" ", builder.command()); | |
| System.out.println(commandString); | |
| Process process = builder.start(); | |
| ProcessResult result = ToolHelper.drainProcessOutputStreams(process, commandString); | |
| if (result.exitCode != 0) { | |
| throw new IllegalStateException(result.toString()); | |
| } | |
| // Fix line separators. | |
| String content = result.stdout; | |
| if (!StringUtils.LINE_SEPARATOR.equals("\n")) { | |
| return content.replace(StringUtils.LINE_SEPARATOR, "\n"); | |
| } | |
| return content; | |
| } | |
| } |