|  | // Copyright (c) 2019, 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.cf; | 
|  |  | 
|  | import com.android.tools.r8.cf.code.CfArithmeticBinop; | 
|  | import com.android.tools.r8.cf.code.CfArrayLength; | 
|  | import com.android.tools.r8.cf.code.CfArrayLoad; | 
|  | import com.android.tools.r8.cf.code.CfArrayStore; | 
|  | import com.android.tools.r8.cf.code.CfCheckCast; | 
|  | import com.android.tools.r8.cf.code.CfCmp; | 
|  | import com.android.tools.r8.cf.code.CfConstClass; | 
|  | import com.android.tools.r8.cf.code.CfConstMethodHandle; | 
|  | import com.android.tools.r8.cf.code.CfConstMethodType; | 
|  | import com.android.tools.r8.cf.code.CfConstNull; | 
|  | import com.android.tools.r8.cf.code.CfConstNumber; | 
|  | import com.android.tools.r8.cf.code.CfConstString; | 
|  | import com.android.tools.r8.cf.code.CfDexItemBasedConstString; | 
|  | import com.android.tools.r8.cf.code.CfFieldInstruction; | 
|  | import com.android.tools.r8.cf.code.CfFrame; | 
|  | import com.android.tools.r8.cf.code.CfGoto; | 
|  | import com.android.tools.r8.cf.code.CfIf; | 
|  | import com.android.tools.r8.cf.code.CfIfCmp; | 
|  | import com.android.tools.r8.cf.code.CfIinc; | 
|  | import com.android.tools.r8.cf.code.CfInstanceOf; | 
|  | import com.android.tools.r8.cf.code.CfInstruction; | 
|  | import com.android.tools.r8.cf.code.CfInvoke; | 
|  | import com.android.tools.r8.cf.code.CfInvokeDynamic; | 
|  | import com.android.tools.r8.cf.code.CfLabel; | 
|  | import com.android.tools.r8.cf.code.CfLoad; | 
|  | import com.android.tools.r8.cf.code.CfLogicalBinop; | 
|  | import com.android.tools.r8.cf.code.CfMonitor; | 
|  | import com.android.tools.r8.cf.code.CfMultiANewArray; | 
|  | import com.android.tools.r8.cf.code.CfNeg; | 
|  | import com.android.tools.r8.cf.code.CfNew; | 
|  | import com.android.tools.r8.cf.code.CfNewArray; | 
|  | import com.android.tools.r8.cf.code.CfNop; | 
|  | import com.android.tools.r8.cf.code.CfNumberConversion; | 
|  | import com.android.tools.r8.cf.code.CfPosition; | 
|  | import com.android.tools.r8.cf.code.CfReturn; | 
|  | import com.android.tools.r8.cf.code.CfReturnVoid; | 
|  | import com.android.tools.r8.cf.code.CfStackInstruction; | 
|  | import com.android.tools.r8.cf.code.CfStore; | 
|  | import com.android.tools.r8.cf.code.CfSwitch; | 
|  | import com.android.tools.r8.cf.code.CfThrow; | 
|  | import com.android.tools.r8.cf.code.CfTryCatch; | 
|  | import com.android.tools.r8.errors.Unimplemented; | 
|  | import com.android.tools.r8.graph.CfCode; | 
|  | import com.android.tools.r8.graph.DexMethod; | 
|  | import com.android.tools.r8.graph.DexProto; | 
|  | import com.android.tools.r8.graph.DexString; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.ir.code.If.Type; | 
|  | import com.android.tools.r8.ir.code.MemberType; | 
|  | import com.android.tools.r8.ir.code.NumericType; | 
|  | import com.android.tools.r8.ir.code.ValueType; | 
|  | import com.android.tools.r8.utils.StringUtils; | 
|  | import com.android.tools.r8.utils.StringUtils.BraceType; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableMap; | 
|  | import it.unimi.dsi.fastutil.objects.Reference2IntMap; | 
|  | import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Arrays; | 
|  | import java.util.Collections; | 
|  | import java.util.HashSet; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  | import java.util.stream.Collectors; | 
|  |  | 
|  | /** Rudimentary printer to print the source representation for creating CfCode object. */ | 
|  | public class CfCodePrinter extends CfPrinter { | 
|  |  | 
|  | private Set<String> imports = new HashSet<>(); | 
|  | private List<String> methods = new ArrayList<>(); | 
|  | private Set<String> methodNames = new HashSet<>(); | 
|  |  | 
|  | // Per method structures. | 
|  |  | 
|  | // Sorted list of labels. | 
|  | private List<CfLabel> sortedLabels = null; | 
|  | // Map from label to its sorted-order index. | 
|  | private Reference2IntMap<CfLabel> labelToIndex = null; | 
|  | private boolean pendingComma = false; | 
|  | private StringBuilder builder = null; | 
|  |  | 
|  | public CfCodePrinter() {} | 
|  |  | 
|  | public List<String> getImports() { | 
|  | ArrayList<String> sorted = new ArrayList<>(imports); | 
|  | sorted.sort(String::compareTo); | 
|  | return sorted; | 
|  | } | 
|  |  | 
|  | public List<String> getMethods() { | 
|  | return methods; | 
|  | } | 
|  |  | 
|  | public void visitMethod(String methodName, CfCode code) { | 
|  | if (!methodNames.add(methodName)) { | 
|  | throw new IllegalStateException( | 
|  | "Invalid attempt to visit the same method twice: " + methodName); | 
|  | } | 
|  | labelToIndex = new Reference2IntOpenHashMap<>(); | 
|  | sortedLabels = new ArrayList<>(); | 
|  | pendingComma = false; | 
|  | builder = | 
|  | new StringBuilder() | 
|  | .append("public static ") | 
|  | .append(r8Type("CfCode", "graph")) | 
|  | .append(" ") | 
|  | .append(methodName) | 
|  | .append("(") | 
|  | .append(r8Type("InternalOptions", "utils")) | 
|  | .append(" options, ") | 
|  | .append(r8Type("DexMethod", "graph")) | 
|  | .append(" method) {"); | 
|  |  | 
|  | for (CfInstruction instruction : code.getInstructions()) { | 
|  | if (instruction instanceof CfLabel) { | 
|  | CfLabel label = (CfLabel) instruction; | 
|  | labelToIndex.put(label, sortedLabels.size()); | 
|  | sortedLabels.add(label); | 
|  | builder | 
|  | .append("CfLabel ") | 
|  | .append(labelName(label)) | 
|  | .append(" = new " + cfType("CfLabel") + "();"); | 
|  | } | 
|  | } | 
|  |  | 
|  | builder | 
|  | .append("return new " + r8Type("CfCode", "graph") + "(") | 
|  | .append("method.holder,") | 
|  | .append(code.getMaxStack()) | 
|  | .append(",") | 
|  | .append(code.getMaxLocals()) | 
|  | .append(",") | 
|  | .append(immutableListType()) | 
|  | .append(".of("); | 
|  |  | 
|  | for (CfInstruction instruction : code.getInstructions()) { | 
|  | instruction.print(this); | 
|  | } | 
|  |  | 
|  | builder.append("),").append(immutableListType()).append(".of("); | 
|  | pendingComma = false; | 
|  | for (CfTryCatch tryCatchRange : code.getTryCatchRanges()) { | 
|  | String guards = | 
|  | tryCatchRange.guards.stream().map(this::dexType).collect(Collectors.joining(", ")); | 
|  | String targets = | 
|  | tryCatchRange.targets.stream().map(this::labelName).collect(Collectors.joining(", ")); | 
|  | printNewInstruction( | 
|  | "CfTryCatch", | 
|  | labelName(tryCatchRange.start), | 
|  | labelName(tryCatchRange.end), | 
|  | immutableListType() + ".of(" + guards + ")", | 
|  | immutableListType() + ".of(" + targets + ")"); | 
|  | } | 
|  |  | 
|  | builder.append("),").append(immutableListType()).append(".of());").append("}"); | 
|  |  | 
|  | methods.add(builder.toString()); | 
|  | } | 
|  |  | 
|  | private String quote(String string) { | 
|  | return "\"" + string + "\""; | 
|  | } | 
|  |  | 
|  | private String longValue(long value) { | 
|  | return (value < Integer.MIN_VALUE || Integer.MAX_VALUE < value) ? (value + "L") : ("" + value); | 
|  | } | 
|  |  | 
|  | // Ensure a type import for a given type. | 
|  | // Note that the package should be given as individual parts to avoid the repackage "fixing" it... | 
|  | private String type(String name, List<String> pkg) { | 
|  | assert !name.contains("."); | 
|  | assert pkg.stream().noneMatch(p -> p.contains(".")); | 
|  | imports.add(String.join(".", pkg) + "." + name); | 
|  | return name; | 
|  | } | 
|  |  | 
|  | private String immutableListType() { | 
|  | return type("ImmutableList", ImmutableList.of("com", "google", "common", "collect")); | 
|  | } | 
|  |  | 
|  | private String r8Type(String name, String pkg) { | 
|  | return r8Type(name, Collections.singletonList(pkg)); | 
|  | } | 
|  |  | 
|  | private String r8Type(String name, List<String> pkg) { | 
|  | return type(name, ImmutableList.<String>builder() | 
|  | .addAll(ImmutableList.of("com", "android", "tools", "r8")) | 
|  | .addAll(pkg).build()); | 
|  | } | 
|  |  | 
|  | private String irType(String name) { | 
|  | return r8Type(name, ImmutableList.of("ir", "code")); | 
|  | } | 
|  |  | 
|  | private String cfType(String name) { | 
|  | return r8Type(name, ImmutableList.of("cf", "code")); | 
|  | } | 
|  |  | 
|  | private String labelName(CfLabel label) { | 
|  | return "label" + labelToIndex.getInt(label); | 
|  | } | 
|  |  | 
|  | private String valueType(ValueType type) { | 
|  | return irType("ValueType") + "." + type.name(); | 
|  | } | 
|  |  | 
|  | private String numericType(NumericType type) { | 
|  | return irType("NumericType") + "." + type.name(); | 
|  | } | 
|  |  | 
|  | private String memberType(MemberType type) { | 
|  | return irType("MemberType") + "." + type.name(); | 
|  | } | 
|  |  | 
|  | private String ifTypeKind(Type kind) { | 
|  | return irType("If") + ".Type." + kind.name(); | 
|  | } | 
|  |  | 
|  | private String dexString(DexString string) { | 
|  | return "options.itemFactory.createString(" + quote(string.toString()) + ")"; | 
|  | } | 
|  |  | 
|  | private final Map<String, String> knownTypeFields = | 
|  | ImmutableMap.<String, String>builder() | 
|  | .put("Z", "booleanType") | 
|  | .put("B", "byteType") | 
|  | .put("C", "charType") | 
|  | .put("D", "doubleType") | 
|  | .put("F", "floatType") | 
|  | .put("I", "intType") | 
|  | .put("J", "longType") | 
|  | .put("S", "shortType") | 
|  | .put("V", "voidType") | 
|  | .put("[Z", "booleanArrayType") | 
|  | .put("[B", "byteArrayType") | 
|  | .put("[C", "charArrayType") | 
|  | .put("[D", "doubleArrayType") | 
|  | .put("[F", "floatArrayType") | 
|  | .put("[I", "intArrayType") | 
|  | .put("[J", "longArrayType") | 
|  | .put("[S", "shortArrayType") | 
|  | .put("Ljava/lang/Object;", "objectType") | 
|  | .put("Ljava/lang/Class;", "classType") | 
|  | .put("Ljava/lang/Throwable;", "throwableType") | 
|  | .put("Ljava/lang/String;", "stringType") | 
|  | .put("Ljava/lang/Character;", "boxedCharType") | 
|  | .put("Ljava/lang/CharSequence;", "charSequenceType") | 
|  | .put("Ljava/lang/StringBuilder;", "stringBuilderType") | 
|  | .put("Ljava/lang/AutoCloseable;", "autoCloseableType") | 
|  | .build(); | 
|  |  | 
|  | private String dexType(DexType type) { | 
|  | String descriptor = type.toDescriptorString(); | 
|  | String field = knownTypeFields.get(descriptor); | 
|  | if (field != null) { | 
|  | return "options.itemFactory." + field; | 
|  | } | 
|  | return "options.itemFactory.createSynthesizedType(" + quote(descriptor) + ")"; | 
|  | } | 
|  |  | 
|  | private String dexProto(DexProto proto) { | 
|  | StringBuilder builder = | 
|  | new StringBuilder() | 
|  | .append("options.itemFactory.createProto(") | 
|  | .append(dexType(proto.returnType)); | 
|  | for (DexType param : proto.parameters.values) { | 
|  | builder.append(", ").append(dexType(param)); | 
|  | } | 
|  | return builder.append(")").toString(); | 
|  | } | 
|  |  | 
|  | private String dexMethod(DexMethod method) { | 
|  | return "options.itemFactory.createMethod(" | 
|  | + dexType(method.holder) | 
|  | + ", " | 
|  | + dexProto(method.proto) | 
|  | + ", " | 
|  | + dexString(method.name) | 
|  | + ")"; | 
|  | } | 
|  |  | 
|  | private void ensureComma() { | 
|  | if (pendingComma) { | 
|  | builder.append(","); | 
|  | } | 
|  | pendingComma = true; | 
|  | } | 
|  |  | 
|  | private void printNewInstruction(String name, String... args) { | 
|  | ensureComma(); | 
|  | builder.append("new ").append(cfType(name)); | 
|  | StringUtils.append(builder, Arrays.asList(args), ", ", BraceType.PARENS); | 
|  | } | 
|  |  | 
|  | private void printNewVarInstruction(String name, ValueType type, int index) { | 
|  | printNewInstruction(name, valueType(type), "" + index); | 
|  | } | 
|  |  | 
|  | private void printNewJumpInstruction(String name, Type kind, ValueType type, CfLabel target) { | 
|  | printNewInstruction(name, ifTypeKind(kind), valueType(type), labelName(target)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfNop nop) { | 
|  | // Since locals and lines are not printed, no need to print nops. | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfStackInstruction instruction) { | 
|  | printNewInstruction( | 
|  | "CfStackInstruction", | 
|  | cfType("CfStackInstruction") + ".Opcode." + instruction.getOpcode().name()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfThrow insn) { | 
|  | printNewInstruction("CfThrow"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfConstNull constNull) { | 
|  | throw new Unimplemented(constNull.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfConstNumber constNumber) { | 
|  | printNewInstruction( | 
|  | "CfConstNumber", longValue(constNumber.getRawValue()), valueType(constNumber.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfConstClass constClass) { | 
|  | throw new Unimplemented(constClass.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfReturnVoid ret) { | 
|  | printNewInstruction("CfReturnVoid"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfReturn ret) { | 
|  | printNewInstruction("CfReturn", valueType(ret.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfMonitor monitor) { | 
|  | throw new Unimplemented(monitor.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfArithmeticBinop arithmeticBinop) { | 
|  | printNewInstruction( | 
|  | "CfArithmeticBinop", | 
|  | cfType("CfArithmeticBinop") + ".Opcode." + arithmeticBinop.getOpcode().name(), | 
|  | numericType(arithmeticBinop.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfCmp cmp) { | 
|  | printNewInstruction( | 
|  | "CfCmp", irType("Cmp") + ".Bias." + cmp.getBias().name(), numericType(cmp.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfLogicalBinop logicalBinop) { | 
|  | printNewInstruction( | 
|  | "CfLogicalBinop", | 
|  | cfType("CfLogicalBinop") + ".Opcode." + logicalBinop.getOpcode().name(), | 
|  | numericType(logicalBinop.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfNeg neg) { | 
|  | printNewInstruction("CfNeg", numericType(neg.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfNumberConversion numberConversion) { | 
|  | printNewInstruction( | 
|  | "CfNumberConversion", | 
|  | numericType(numberConversion.getFromType()), | 
|  | numericType(numberConversion.getToType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfConstString constString) { | 
|  | printNewInstruction("CfConstString", dexString(constString.getString())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfDexItemBasedConstString constString) { | 
|  | throw new Unimplemented(constString.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfArrayLoad arrayLoad) { | 
|  | printNewInstruction("CfArrayLoad", memberType(arrayLoad.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfArrayStore arrayStore) { | 
|  | printNewInstruction("CfArrayStore", memberType(arrayStore.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfInvoke invoke) { | 
|  | printNewInstruction( | 
|  | "CfInvoke", | 
|  | Integer.toString(invoke.getOpcode()), | 
|  | dexMethod(invoke.getMethod()), | 
|  | Boolean.toString(invoke.isInterface())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfInvokeDynamic invoke) { | 
|  | throw new Unimplemented(invoke.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfFrame frame) { | 
|  | throw new Unimplemented(frame.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfInstanceOf insn) { | 
|  | printNewInstruction("CfInstanceOf", dexType(insn.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfCheckCast insn) { | 
|  | printNewInstruction("CfCheckCast", dexType(insn.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfFieldInstruction insn) { | 
|  | throw new Unimplemented(insn.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfNew newInstance) { | 
|  | printNewInstruction("CfNew", dexType(newInstance.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfNewArray newArray) { | 
|  | printNewInstruction("CfNewArray", dexType(newArray.getType())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfMultiANewArray multiANewArray) { | 
|  | throw new Unimplemented(multiANewArray.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfArrayLength arrayLength) { | 
|  | printNewInstruction("CfArrayLength"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfLabel label) { | 
|  | ensureComma(); | 
|  | builder.append(labelName(label)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfPosition instruction) { | 
|  | // Ignoring positions. | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfGoto jump) { | 
|  | printNewInstruction("CfGoto", labelName(jump.getTarget())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfIf conditional) { | 
|  | printNewJumpInstruction( | 
|  | "CfIf", conditional.getKind(), conditional.getType(), conditional.getTarget()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfIfCmp conditional) { | 
|  | printNewJumpInstruction( | 
|  | "CfIfCmp", conditional.getKind(), conditional.getType(), conditional.getTarget()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfSwitch cfSwitch) { | 
|  | throw new Unimplemented(cfSwitch.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfLoad load) { | 
|  | printNewVarInstruction("CfLoad", load.getType(), load.getLocalIndex()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfStore store) { | 
|  | printNewVarInstruction("CfStore", store.getType(), store.getLocalIndex()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfIinc instruction) { | 
|  | printNewInstruction( | 
|  | "CfIinc", | 
|  | Integer.toString(instruction.getLocalIndex()), | 
|  | Integer.toString(instruction.getIncrement())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfConstMethodHandle handle) { | 
|  | throw new Unimplemented(handle.getClass().getSimpleName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void print(CfConstMethodType type) { | 
|  | throw new Unimplemented(type.getClass().getSimpleName()); | 
|  | } | 
|  | } |