All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.yawk.valda.ir.printer.CodePrinter Maven / Gradle / Ivy

The newest version!
package at.yawk.valda.ir.printer;

import at.yawk.valda.ir.FieldMirror;
import at.yawk.valda.ir.MethodMirror;
import at.yawk.valda.ir.TypeMirror;
import at.yawk.valda.ir.code.ArrayLength;
import at.yawk.valda.ir.code.ArrayLoadStore;
import at.yawk.valda.ir.code.BasicBlock;
import at.yawk.valda.ir.code.BinaryOperation;
import at.yawk.valda.ir.code.Branch;
import at.yawk.valda.ir.code.CheckCast;
import at.yawk.valda.ir.code.Const;
import at.yawk.valda.ir.code.FillArray;
import at.yawk.valda.ir.code.GoTo;
import at.yawk.valda.ir.code.InstanceOf;
import at.yawk.valda.ir.code.Instruction;
import at.yawk.valda.ir.code.Invoke;
import at.yawk.valda.ir.code.LiteralBinaryOperation;
import at.yawk.valda.ir.code.LoadStore;
import at.yawk.valda.ir.code.LocalVariable;
import at.yawk.valda.ir.code.MethodBody;
import at.yawk.valda.ir.code.Monitor;
import at.yawk.valda.ir.code.Move;
import at.yawk.valda.ir.code.NewArray;
import at.yawk.valda.ir.code.Return;
import at.yawk.valda.ir.code.Switch;
import at.yawk.valda.ir.code.TerminatingInstruction;
import at.yawk.valda.ir.code.Throw;
import at.yawk.valda.ir.code.Try;
import at.yawk.valda.ir.code.UnaryOperation;
import com.google.common.base.Splitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
import org.eclipse.collections.api.map.primitive.ObjectIntMap;
import org.eclipse.collections.api.set.primitive.IntSet;
import org.eclipse.collections.api.set.primitive.MutableIntSet;
import org.eclipse.collections.impl.factory.primitive.IntSets;
import org.eclipse.collections.impl.factory.primitive.ObjectIntMaps;

/**
 * @author yawkat
 */
@RequiredArgsConstructor
public final class CodePrinter {
    private final MethodBody body;

    @Setter private boolean showIdentity = false;

    private boolean built = false;

    private final List blocks = new ArrayList<>();
    private final List lines = new ArrayList<>();
    private final List references = new ArrayList<>();
    private ObjectIntMap blockStarts;

    private int indent = 0;

    private void visitBlockForIndex(BasicBlock block) {
        if (blocks.contains(block)) { return; }
        blocks.add(block);

        TerminatingInstruction terminatingInstruction = block.getTerminatingInstruction();
        for (BasicBlock successor : terminatingInstruction.getSuccessors()) {
            visitBlockForIndex(successor);
        }
        if (block.getTry() != null) {
            for (Try.Catch handler : block.getTry().getHandlers()) {
                visitBlockForIndex(handler.getHandler());
            }
        }
    }

    private void printLine(String line) {
        for (int i = 0; i < indent; i++) {
            //noinspection StringConcatenationInLoop
            line = " " + line;
        }
        lines.add(line);
    }

    private void printLine(String line, Object... parameters) {
        StringBuilder out = new StringBuilder();
        Iterator parts = Splitter.on("%s").split(line).iterator();
        for (Object parameter : parameters) {
            out.append(parts.next());
            appendParameter(out, parameter);
        }
        out.append(parts.next());
        if (parts.hasNext()) {
            throw new IllegalArgumentException("Format string underflow");
        }
        printLine(out.toString());
    }

    private void appendParameter(StringBuilder out, Object parameter) {
        if (parameter instanceof LocalVariable) {
            out.append(getVariableName((LocalVariable) parameter));
        } else if (parameter instanceof BasicBlock) {
            out.append(reference((BasicBlock) parameter));
        } else if (parameter instanceof String) {
            out.append(parameter);
        } else if (parameter instanceof Integer) {
            out.append("0x").append(Integer.toHexString((Integer) parameter));
        } else if (parameter instanceof Short) {
            out.append("0x").append(Integer.toHexString(((Short) parameter) & 0xffff));
        } else if (parameter instanceof Long) {
            out.append("0x").append(Long.toHexString((Long) parameter)).append("L");
        } else if (parameter instanceof Enum) {
            out.append(((Enum) parameter).name().toLowerCase().replace("_", "-"));
        } else if (parameter instanceof Iterable) {
            out.append("[");
            boolean first = true;
            for (Object o : (Iterable) parameter) {
                if (!first) {
                    out.append(", ");
                }
                first = false;
                appendParameter(out, o);
            }
            out.append("]");
        } else if (parameter instanceof TypeMirror) {
            out.append(type((TypeMirror) parameter));
        } else if (parameter instanceof MethodMirror) {
            out.append(method((MethodMirror) parameter));
        } else if (parameter instanceof FieldMirror) {
            out.append(field((FieldMirror) parameter));
        } else {
            throw new UnsupportedOperationException("Parameter " + parameter);
        }
    }

    private String reference(BasicBlock block) {
        boolean found = false;
        for (Reference reference : references) {
            if (reference.to.equals(block)) {
                reference.from.add(lines.size());
                found = true;
                break;
            }
        }
        if (!found) {
            Reference reference = new Reference(block);
            reference.from.add(lines.size());
            references.add(reference);
        }
        return getBlockName(block);
    }

    private void indent(Runnable r) {
        indent++;
        try {
            r.run();
        } finally {
            indent--;
        }
    }

    public void print(String indent, Appendable out) throws IOException {
        if (!built) {
            visitBlockForIndex(body.getEntryPoint());
            MutableObjectIntMap blockStarts = ObjectIntMaps.mutable.empty();
            for (BasicBlock block : blocks) {
                blockStarts.put(block, lines.size());
                printBlock(block);
            }
            this.blockStarts = blockStarts;
            references.sort(Comparator
                                    .comparingInt(Reference::firstLine)
                                    .thenComparingInt(r -> ~r.lastLine())
                                    .reversed());
            List openRefs = new ArrayList<>();
            int levels = 0;
            for (int i = 0; i < lines.size(); i++) {
                for (Reference ref : references) {
                    if (ref.firstLine() == i) {
                        ref.level = -1;
                        boolean conflict;
                        do {
                            ref.level++;
                            conflict = false;
                            for (Reference openRef : openRefs) {
                                if (openRef.level == ref.level) {
                                    conflict = true;
                                    break;
                                }
                            }
                        } while (conflict);
                        openRefs.add(ref);
                        levels = Math.max(ref.level + 1, levels);
                    }
                    if (ref.lastLine() == i - 1) {
                        openRefs.remove(ref);
                    }
                }
            }
            openRefs.clear();
            for (int i = 0; i < lines.size(); i++) {
                for (Reference reference : references) {
                    if (reference.firstLine() == i) {
                        openRefs.add(reference);
                    }
                    if (reference.lastLine() == i - 1) {
                        openRefs.remove(reference);
                    }
                }
                int[] refString = new int[levels + 1];
                int fillUntil = levels + 1;
                for (Reference ref : openRefs) {
                    int j = levels - ref.level - 1;
                    if (refString[j] != 0) { throw new AssertionError(openRefs); }
                    if (ref.getLines().contains(i)) {
                        if (ref.firstLine() == i) {
                            refString[j] = '┌';
                        } else if (ref.lastLine() == i) {
                            refString[j] = '└';
                        } else {
                            refString[j] = '├';
                        }
                        fillUntil = Math.min(fillUntil, j);
                        refString[levels] = ref.from.contains(i) ? '─' : '→';
                    } else {
                        refString[j] = '│';
                    }
                }
                for (int j = 0; j < refString.length; j++) {
                    if (refString[j] == 0) {
                        refString[j] = j < fillUntil ? ' ' : '─';
                    }
                }
                lines.set(i, new String(refString, 0, refString.length) + lines.get(i));
            }

            built = true;
        }

        for (String line : lines) {
            out.append(indent).append(line).append('\n');
        }
    }

    private void printBlock(BasicBlock block) {
        if (showIdentity) {
            printLine("%s (%s):", getBlockName(block), String.format("%08x", System.identityHashCode(block)));
        } else {
            printLine("%s:", getBlockName(block));
        }
        indent(() -> {
            if (block.equals(body.getEntryPoint()) && !body.getParameters().isEmpty()) {
                printLine("Parameters:");
                indent(() -> {
                    for (LocalVariable variable : body.getParameters()) {
                        printLine("Parameter " + getVariableName(variable));
                    }
                });
            }
            if (block.getExceptionVariable() != null) {
                printLine("Exception variable: " + getVariableName(block.getExceptionVariable()));
            }
            for (Instruction instruction : block.getInstructions()) {
                printInstruction(instruction);
            }
            if (block.getTry() != null) {
                printLine("Exceptions: ");
                indent(() -> {
                    for (Try.Catch handler : block.getTry().getHandlers()) {
                        TypeMirror exceptionType = handler.getExceptionType();
                        printLine("%s: %s", exceptionType == null ? "any" : type(exceptionType), handler.getHandler());
                    }
                });
            }
        });
    }

    private void printInstruction(Instruction instruction) {
        if (showIdentity) {
            printLine("insn#%s", String.format("%08x", System.identityHashCode(instruction)));
            indent++;
        }
        if (instruction instanceof ArrayLength) {
            printLine("array-length %s <- %s",
                      ((ArrayLength) instruction).getTarget(),
                      ((ArrayLength) instruction).getOperand());
        } else if (instruction instanceof ArrayLoadStore) {
            ArrayLoadStore loadStore = (ArrayLoadStore) instruction;
            if (loadStore.getType() == LoadStore.Type.STORE) {
                printLine("array-store-%s %s[%s] <- %s",
                          loadStore.getElementType(),
                          loadStore.getArray(), loadStore.getIndex(), loadStore.getValue());
            } else {
                printLine("array-load-%s %s <- %s[%s]",
                          loadStore.getElementType(),
                          loadStore.getValue(),
                          loadStore.getArray(),
                          loadStore.getIndex());
            }
        } else if (instruction instanceof BinaryOperation) {
            BinaryOperation binary = (BinaryOperation) instruction;
            printLine("%s %s <- %s, %s", binary.getType(), binary.getDestination(), binary.getLhs(), binary.getRhs());
        } else if (instruction instanceof Branch) {
            Branch branch = (Branch) instruction;
            if (branch.getRhs() == null) {
                printLine("%s %s, 0 ? %s : %s",
                          branch.getType(),
                          branch.getLhs(),
                          branch.getBranchTrue(),
                          branch.getBranchFalse());
            } else {
                printLine("%s %s, %s ? %s : %s",
                          branch.getType(),
                          branch.getLhs(),
                          branch.getRhs(),
                          branch.getBranchTrue(),
                          branch.getBranchFalse());
            }
        } else if (instruction instanceof CheckCast) {
            printLine("check-cast %s %s", ((CheckCast) instruction).getVariable(), ((CheckCast) instruction).getType());
        } else if (instruction instanceof Const) {
            Const.Value value = ((Const) instruction).getValue();
            Object valueObject;
            if (value instanceof Const.String) {
                String unescaped = ((Const.String) value).getValue();
                StringBuilder escaped = new StringBuilder();
                for (int i = 0; i < unescaped.length(); i++) {
                    char c = unescaped.charAt(i);
                    if (c == '\\') {
                        escaped.append("\\\\");
                    } else if (c >= 0x30 && c < 0x7f) {
                        escaped.append(c);
                    } else {
                        escaped.append("\\u").append(String.format("%04x", (int) c));
                    }
                }
                valueObject = escaped.toString();
            } else if (value instanceof Const.Null) {
                valueObject = "null";
            } else if (value instanceof Const.Narrow) {
                valueObject = ((Const.Narrow) value).getValue();
            } else if (value instanceof Const.Wide) {
                valueObject = ((Const.Wide) value).getValue();
            } else if (value instanceof Const.Class) {
                valueObject = ((Const.Class) value).getValue();
            } else {
                throw new AssertionError();
            }
            printLine("const %s <- %s", ((Const) instruction).getTarget(), valueObject);
        } else if (instruction instanceof FillArray) {
            printLine("fill-array %s <- %s",
                      ((FillArray) instruction).getArray(),
                      ((FillArray) instruction).getContents().toString());
        } else if (instruction instanceof GoTo) {
            printLine("goto %s", ((GoTo) instruction).getTarget());
        } else if (instruction instanceof InstanceOf) {
            InstanceOf instanceOf = (InstanceOf) instruction;
            printLine("instance-of %s <- %s, %s",
                      instanceOf.getTarget(),
                      instanceOf.getOperand(),
                      instanceOf.getType());
        } else if (instruction instanceof Invoke) {
            Invoke invoke = (Invoke) instruction;
            String name;
            switch (invoke.getType()) {
                case NORMAL: {
                    name = "invoke";
                    break;
                }
                case SPECIAL: {
                    name = "invoke-special";
                    break;
                }
                case NEW_INSTANCE: {
                    name = "new-instance";
                    break;
                }
                default:
                    throw new AssertionError();
            }
            if (invoke.getMethod().isStatic()) {
                name = "invoke-static";
            }
            if (invoke.getReturnValue() == null) {
                printLine("%s %s, %s", name, invoke.getParameters(), invoke.getMethod());
            } else {
                printLine("%s %s <- %s, %s", name, invoke.getReturnValue(), invoke.getParameters(), invoke.getMethod());
            }
        } else if (instruction instanceof LiteralBinaryOperation) {
            LiteralBinaryOperation operation = (LiteralBinaryOperation) instruction;
            printLine("%s %s <- %s, %s",
                      operation.getType(),
                      operation.getDestination(),
                      operation.getLhs(),
                      operation.getRhs());
        } else if (instruction instanceof LoadStore) {
            LoadStore loadStore = (LoadStore) instruction;
            if (loadStore.getType() == LoadStore.Type.STORE) {
                if (loadStore.getInstance() == null) {
                    printLine("store %s <- %s", loadStore.getField(), loadStore.getValue());
                } else {
                    printLine("store %s %s <- %s", loadStore.getInstance(), loadStore.getField(), loadStore.getValue());
                }
            } else {
                if (loadStore.getInstance() == null) {
                    printLine("load %s <- %s", loadStore.getValue(), loadStore.getField());
                } else {
                    printLine("load %s <- %s %s", loadStore.getValue(), loadStore.getInstance(), loadStore.getField());
                }
            }
        } else if (instruction instanceof Monitor) {
            printLine("monitor-%s %s", ((Monitor) instruction).getType(), ((Monitor) instruction).getMonitor());
        } else if (instruction instanceof Move) {
            printLine("move %s <- %s", ((Move) instruction).getTo(), ((Move) instruction).getFrom());
        } else if (instruction instanceof NewArray) {
            NewArray newArray = (NewArray) instruction;
            if (newArray.hasVariables()) {
                printLine("new-array %s <- %s %s", newArray.getTarget(), newArray.getType(), newArray.getVariables());
            } else {
                printLine("new-array %s <- %s %s", newArray.getTarget(), newArray.getType(), newArray.getLength());
            }
        } else if (instruction instanceof Return) {
            if (((Return) instruction).getReturnValue() == null) {
                printLine("return");
            } else {
                printLine("return %s", ((Return) instruction).getReturnValue());
            }
        } else if (instruction instanceof Switch) {
            printLine("switch %s", ((Switch) instruction).getOperand());
            indent(() -> {
                ((Switch) instruction).getBranches().forEachKeyValue((k, v) -> printLine("%s -> %s", k, v));
                printLine("default -> %s", ((Switch) instruction).getDefaultBranch());
            });
        } else if (instruction instanceof Throw) {
            printLine("throw %s", ((Throw) instruction).getException());
        } else if (instruction instanceof UnaryOperation) {
            printLine("%s %s <- %s",
                      ((UnaryOperation) instruction).getType(),
                      ((UnaryOperation) instruction).getDestination(),
                      ((UnaryOperation) instruction).getSource());
        } else {
            printLine(instruction.toString());
        }
        if (showIdentity) {
            indent--;
        }
    }

    private String getVariableName(LocalVariable variable) {
        return variable.getName();
    }

    private String type(TypeMirror typeMirror) {
        return typeMirror.getType().getDescriptor();
    }

    private String method(MethodMirror methodMirror) {
        return methodMirror.getDebugDescriptor();
    }

    private String field(FieldMirror fieldMirror) {
        return type(fieldMirror.getDeclaringType()) + "->" + fieldMirror.getName() + ":" +
               type(fieldMirror.getType());
    }

    private String getBlockName(BasicBlock block) {
        int i = blocks.indexOf(block);
        if (i == -1) { throw new NoSuchElementException(); }
        return "Block #" + (i + 1);
    }

    @ToString
    @RequiredArgsConstructor
    private class Reference {
        final MutableIntSet from = IntSets.mutable.empty();
        final BasicBlock to;

        @Getter(lazy = true) private final IntSet lines = computeLines();

        int level;

        int toLine() {
            return blockStarts.getOrThrow(to);
        }

        IntSet computeLines() {
            MutableIntSet lines = IntSets.mutable.ofAll(from);
            lines.add(toLine());
            return lines;
        }

        int firstLine() {
            return getLines().min();
        }

        int lastLine() {
            return getLines().max();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy