| // Copyright (c) 2022, 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.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.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.MethodReference; |
| import com.android.tools.r8.utils.Box; |
| 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 java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| public abstract class CfClassGenerator extends CodeGenerationBase { |
| |
| private final CfCodeGeneratorImportCollection imports = new CfCodeGeneratorImportCollection(); |
| |
| public abstract Class<?> getImplementation(); |
| |
| public String generateClass() throws IOException { |
| return formatRawOutput(generateRawOutput()); |
| } |
| |
| private String generateRawOutput() throws IOException { |
| String classDeclaration = generateClassDeclaration(); |
| return StringUtils.lines(getHeaderString(), imports.generateImports(), classDeclaration); |
| } |
| |
| private String generateClassDeclaration() throws IOException { |
| JavaStringBuilder builder = new JavaStringBuilder(); |
| builder.append("public final class " + getGeneratedClassName() + " ").appendOpeningBrace(); |
| DexProgramClass clazz = readImplementationClass(); |
| generateCreateClassMethod(builder, clazz); |
| generateCreateFieldsMethod(builder, "createInstanceFields", clazz.instanceFields()); |
| generateCreateFieldsMethod(builder, "createStaticFields", clazz.staticFields()); |
| CfCodePrinter codePrinter = new CfCodePrinter(); |
| Map<MethodReference, String> createCfCodeMethodNames = |
| generateCreateCfCodeMethods(clazz, codePrinter); |
| generateCreateMethodsMethod( |
| builder, "createDirectMethods", clazz.directMethods(), createCfCodeMethodNames); |
| generateCreateMethodsMethod( |
| builder, "createVirtualMethods", clazz.virtualMethods(), createCfCodeMethodNames); |
| codePrinter.getMethods().forEach(builder::appendLine); |
| builder.appendClosingBrace(); |
| return builder.toString(); |
| } |
| |
| private DexProgramClass readImplementationClass() throws IOException { |
| InternalOptions options = new InternalOptions(factory, new Reporter()); |
| options.testing.readInputStackMaps = true; |
| Box<DexProgramClass> result = new Box<>(); |
| JarClassFileReader<DexProgramClass> reader = |
| new JarClassFileReader<>(new JarApplicationReader(options), result::set, ClassKind.PROGRAM); |
| reader.read(Origin.unknown(), ToolHelper.getClassAsBytes(getImplementation())); |
| return result.get(); |
| } |
| |
| private void generateCreateClassMethod(JavaStringBuilder builder, DexProgramClass clazz) { |
| builder |
| .startLine() |
| .append("public static ") |
| .append(imports.getDexProgramClass()) |
| .append(" createClass(") |
| .append(imports.getDexItemFactory()) |
| .append(" dexItemFactory) ") |
| .appendOpeningBrace(); |
| |
| builder |
| .startLine() |
| .append("return new ") |
| .append(imports.getDexProgramClass()) |
| .appendOpeningMultiLineParenthesis(); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createType(\"") |
| .append(clazz.getType().toDescriptorString()) |
| .appendLine("\"),"); |
| |
| builder.startLine().append(imports.getProgramResourceKind()).appendLine(".CF,"); |
| |
| builder.startLine().append(imports.getOrigin()).appendLine(".unknown(),"); |
| |
| builder |
| .startLine() |
| .append(imports.getClassAccessFlags()) |
| .append(".fromCfAccessFlags(") |
| .append(clazz.getAccessFlags().getAsCfAccessFlags()) |
| .appendLine("),"); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createType(\"") |
| .append(clazz.getSuperType().toDescriptorString()) |
| .appendLine("\"),"); |
| |
| builder.startLine().append(imports.getDexTypeList()).appendLine(".empty(),"); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createString(\"") |
| .append(clazz.getSourceFile().toString()) |
| .appendLine("\"),"); |
| |
| builder.startLine().append(imports.getNestHostClassAttribute()).appendLine(".none(),"); |
| |
| for (int i = 0; i < 2; i++) { |
| builder.startLine().append(imports.getJavaUtilCollections()).appendLine(".emptyList(),"); |
| } |
| |
| builder.startLine().append(imports.getEnclosingMethodAttribute()).appendLine(".none(),"); |
| |
| builder.startLine().append(imports.getJavaUtilCollections()).appendLine(".emptyList(),"); |
| |
| builder.startLine().append(imports.getClassSignature()).appendLine(".noSignature(),"); |
| |
| builder.startLine().append(imports.getDexAnnotationSet()).appendLine(".empty(),"); |
| |
| builder.startLine().appendLine("createStaticFields(dexItemFactory),"); |
| |
| builder.startLine().appendLine("createInstanceFields(dexItemFactory),"); |
| |
| builder |
| .startLine() |
| .append(imports.getMethodCollectionFactory()) |
| .appendLine( |
| ".fromMethods(createDirectMethods(dexItemFactory)," |
| + " createVirtualMethods(dexItemFactory)),"); |
| |
| builder.startLine().appendLine("dexItemFactory.getSkipNameValidationForTesting(),"); |
| |
| builder.startLine().append(imports.getDexProgramClass()).append("::invalidChecksumRequest"); |
| |
| builder.appendClosingMultiLineParenthesis().appendLine(';'); |
| builder.appendClosingBrace(); |
| } |
| |
| private void generateCreateFieldsMethod( |
| JavaStringBuilder builder, String methodName, List<DexEncodedField> fields) { |
| builder |
| .startLine() |
| .append("private static ") |
| .append(imports.getDexEncodedField()) |
| .append("[] ") |
| .append(methodName) |
| .append("(") |
| .append(imports.getDexItemFactory()) |
| .append(" dexItemFactory) ") |
| .appendOpeningBrace(); |
| |
| builder |
| .startLine() |
| .append("return new ") |
| .append(imports.getDexEncodedField()) |
| .append("[] ") |
| .appendOpeningArrayBrace(); |
| |
| Iterator<DexEncodedField> fieldIterator = fields.iterator(); |
| while (fieldIterator.hasNext()) { |
| DexEncodedField field = fieldIterator.next(); |
| assert !field.isStatic() || !field.hasExplicitStaticValue(); |
| |
| builder |
| .startLine() |
| .append(imports.getDexEncodedField()) |
| .appendLine(".syntheticBuilder()") |
| .indent(4); |
| |
| builder.startLine().append(".setField").appendOpeningMultiLineParenthesis(); |
| |
| builder.startLine().append("dexItemFactory.createField").appendOpeningMultiLineParenthesis(); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createType(\"") |
| .append(field.getHolderType().toDescriptorString()) |
| .appendLine("\"),"); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createType(\"") |
| .append(field.getType().toDescriptorString()) |
| .appendLine("\"),"); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createString(\"") |
| .append(field.getName().toString()) |
| .append("\")") |
| .appendClosingMultiLineParenthesis() |
| .appendClosingMultiLineParenthesis() |
| .appendLine(); |
| |
| builder |
| .startLine() |
| .append(".setAccessFlags(") |
| .append(imports.getFieldAccessFlags()) |
| .append(".fromCfAccessFlags(") |
| .append(field.getAccessFlags().getAsCfAccessFlags()) |
| .appendLine("))"); |
| |
| builder |
| .startLine() |
| .append(".setApiLevel(") |
| .append(imports.getComputedApiLevel()) |
| .appendLine(".unknown())"); |
| |
| builder.startLine().append(".build()").indent(-4); |
| if (fieldIterator.hasNext()) { |
| builder.appendLine(','); |
| } else { |
| builder.appendLine(); |
| } |
| } |
| |
| builder.appendClosingArrayBrace(); |
| builder.appendClosingBrace(); |
| } |
| |
| private void generateCreateMethodsMethod( |
| JavaStringBuilder builder, |
| String methodName, |
| Iterable<DexEncodedMethod> methods, |
| Map<MethodReference, String> createCfCodeMethodNames) { |
| builder |
| .startLine() |
| .append("private static ") |
| .append(imports.getDexEncodedMethod()) |
| .append("[] ") |
| .append(methodName) |
| .append("(") |
| .append(imports.getDexItemFactory()) |
| .append(" dexItemFactory) ") |
| .appendOpeningBrace(); |
| |
| builder |
| .startLine() |
| .append("return new ") |
| .append(imports.getDexEncodedMethod()) |
| .append("[] ") |
| .appendOpeningArrayBrace(); |
| |
| Iterator<DexEncodedMethod> methodIterator = methods.iterator(); |
| while (methodIterator.hasNext()) { |
| DexEncodedMethod method = methodIterator.next(); |
| builder |
| .startLine() |
| .append(imports.getDexEncodedMethod()) |
| .appendLine(".syntheticBuilder()") |
| .indent(4); |
| |
| builder |
| .startLine() |
| .append(".setAccessFlags(") |
| .append(imports.getMethodAccessFlags()) |
| .append(".fromCfAccessFlags(") |
| .append(method.getAccessFlags().getAsCfAccessFlags()) |
| .append(", ") |
| .append(method.isInitializer()) |
| .appendLine("))"); |
| |
| builder |
| .startLine() |
| .append(".setApiLevelForCode(") |
| .append(imports.getComputedApiLevel()) |
| .appendLine(".unknown())"); |
| |
| builder |
| .startLine() |
| .append(".setApiLevelForDefinition(") |
| .append(imports.getComputedApiLevel()) |
| .appendLine(".unknown())"); |
| |
| builder |
| .startLine() |
| .append(".setClassFileVersion(") |
| .append(imports.getCfVersion()) |
| .appendLine(".V1_8)"); |
| |
| builder.startLine().append(".setMethod").appendOpeningMultiLineParenthesis(); |
| |
| builder.startLine().append("dexItemFactory.createMethod").appendOpeningMultiLineParenthesis(); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createType(\"") |
| .append(method.getHolderType().toDescriptorString()) |
| .appendLine("\"),"); |
| |
| builder.startLine().append("dexItemFactory.createProto").appendOpeningMultiLineParenthesis(); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createType(\"") |
| .append(method.getReturnType().toDescriptorString()) |
| .append("\")"); |
| |
| for (DexType parameter : method.getParameters()) { |
| builder |
| .appendLine(",") |
| .startLine() |
| .append("dexItemFactory.createType(\"") |
| .append(parameter.toDescriptorString()) |
| .append("\")"); |
| } |
| builder.appendClosingMultiLineParenthesis().appendLine(','); |
| |
| builder |
| .startLine() |
| .append("dexItemFactory.createString(\"") |
| .append(method.getName().toString()) |
| .append("\")"); |
| |
| builder.appendClosingMultiLineParenthesis().appendClosingMultiLineParenthesis().appendLine(); |
| |
| String createCfCodeMethodName = |
| createCfCodeMethodNames.get(method.getReference().asMethodReference()); |
| if (createCfCodeMethodName != null) { |
| builder |
| .startLine() |
| .append(".setCode(method -> ") |
| .append(createCfCodeMethodName) |
| .appendLine("(dexItemFactory, method))"); |
| } |
| |
| builder.startLine().append(".build()").indent(-4); |
| if (methodIterator.hasNext()) { |
| builder.appendLine(','); |
| } else { |
| builder.appendLine(); |
| } |
| } |
| |
| builder.appendClosingArrayBrace(); |
| builder.appendClosingBrace(); |
| } |
| |
| private Map<MethodReference, String> generateCreateCfCodeMethods( |
| DexProgramClass clazz, CfCodePrinter codePrinter) { |
| Map<MethodReference, String> createCfCodeMethodNames = new HashMap<>(); |
| int index = 0; |
| for (DexEncodedMethod method : clazz.allMethodsSorted()) { |
| if (!method.hasCode()) { |
| continue; |
| } |
| String generatedMethodName = getCreateCfCodeMethodName(method, index); |
| createCfCodeMethodNames.put(method.getReference().asMethodReference(), generatedMethodName); |
| codePrinter.visitMethod(generatedMethodName, method.getCode().asCfCode()); |
| index++; |
| } |
| codePrinter.getImportsSorted().forEach(imports::addImport); |
| return createCfCodeMethodNames; |
| } |
| |
| private String getCreateCfCodeMethodName(DexEncodedMethod method, int index) { |
| if (method.isClassInitializer()) { |
| return "createClassInitializerCfCode"; |
| } |
| if (method.isInstanceInitializer()) { |
| return "createInstanceInitializerCfCode" + index; |
| } |
| return "createCfCode" + index + "_" + method.getName().toString(); |
| } |
| |
| public void writeClassToFile() throws IOException { |
| FileUtils.writeToFile(getGeneratedFile(), null, generateClass().getBytes()); |
| } |
| } |
| |