/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cfg;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionWithNext;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.*;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction;
import org.jetbrains.kotlin.psi.KtElement;
import org.jetbrains.kotlin.psi.KtNamedDeclaration;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.*;
public class CFGraphToDotFilePrinter {
public static void dumpDot(File file, Collection<Pseudocode> pseudocodes) throws FileNotFoundException {
File target = KotlinTestUtils.replaceExtension(file, "dot");
PrintStream out = new PrintStream(target);
out.println("digraph " + FileUtil.getNameWithoutExtension(file) + " {");
int[] count = new int[1];
Map<Instruction, String> nodeToName = new HashMap<>();
for (Pseudocode pseudocode : pseudocodes) {
dumpNodes(pseudocode.getInstructionsIncludingDeadCode(), out, count, nodeToName, Sets
.newHashSet(pseudocode.getInstructions()));
}
int i = 0;
for (Pseudocode pseudocode : pseudocodes) {
String label;
KtElement correspondingElement = pseudocode.getCorrespondingElement();
if (correspondingElement instanceof KtNamedDeclaration) {
KtNamedDeclaration namedDeclaration = (KtNamedDeclaration) correspondingElement;
label = namedDeclaration.getName();
}
else {
label = "anonymous_" + i;
}
out.println("subgraph cluster_" + i + " {\n" +
"label=\"" + label + "\";\n" +
"color=blue;\n");
dumpEdges(pseudocode.getInstructionsIncludingDeadCode(), out, count, nodeToName);
out.println("}");
i++;
}
out.println("}");
out.close();
}
private static void dumpEdges(List<Instruction> instructions, PrintStream out, int[] count, Map<Instruction, String> nodeToName) {
for (Instruction fromInst : instructions) {
fromInst.accept(new InstructionVisitor() {
@Override
public void visitLocalFunctionDeclarationInstruction(@NotNull LocalFunctionDeclarationInstruction instruction) {
int index = count[0];
// instruction.getBody().dumpSubgraph(out, "subgraph cluster_" + index, count, "color=blue;\nlabel = \"f" + index + "\";", nodeToName);
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getBody().getInstructionsIncludingDeadCode().get(0)), null);
visitInstructionWithNext(instruction);
}
@Override
public void visitUnconditionalJump(@NotNull UnconditionalJumpInstruction instruction) {
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getResolvedTarget()), null);
}
@Override
public void visitJump(@NotNull AbstractJumpInstruction instruction) {
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getResolvedTarget()), null);
}
@Override
public void visitNondeterministicJump(@NotNull NondeterministicJumpInstruction instruction) {
for (Instruction nextInstruction : instruction.getNextInstructions()) {
printEdge(out, nodeToName.get(instruction), nodeToName.get(nextInstruction), null);
}
}
@Override
public void visitReturnValue(@NotNull ReturnValueInstruction instruction) {
super.visitReturnValue(instruction);
}
@Override
public void visitReturnNoValue(@NotNull ReturnNoValueInstruction instruction) {
super.visitReturnNoValue(instruction);
}
@Override
public void visitConditionalJump(@NotNull ConditionalJumpInstruction instruction) {
String from = nodeToName.get(instruction);
printEdge(out, from, nodeToName.get(instruction.getNextOnFalse()), "no");
printEdge(out, from, nodeToName.get(instruction.getNextOnTrue()), "yes");
}
@Override
public void visitInstructionWithNext(@NotNull InstructionWithNext instruction) {
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getNext()), null);
}
@Override
public void visitSubroutineExit(@NotNull SubroutineExitInstruction instruction) {
if (!instruction.getNextInstructions().isEmpty()) {
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getNextInstructions().iterator().next()), null);
}
}
@Override
public void visitSubroutineSink(@NotNull SubroutineSinkInstruction instruction) {
// Nothing
}
@Override
public void visitInstruction(@NotNull Instruction instruction) {
throw new UnsupportedOperationException(instruction.toString());
}
});
}
}
private static void dumpNodes(List<Instruction> instructions, PrintStream out, int[] count, Map<Instruction, String> nodeToName, Set<Instruction> remainedAfterPostProcessInstructions) {
for (Instruction node : instructions) {
String name = "n" + count[0]++;
nodeToName.put(node, name);
String text = node.toString();
int newline = text.indexOf("\n");
if (newline >= 0) {
text = text.substring(0, newline);
}
String shape = "box";
if (node instanceof ConditionalJumpInstruction || node instanceof UnconditionalJumpInstruction) {
shape = "diamond";
}
else if (node instanceof NondeterministicJumpInstruction) {
shape = "Mdiamond";
}
else if (node instanceof MagicInstruction && ((MagicInstruction) node).getKind() == MagicKind.UNSUPPORTED_ELEMENT) {
shape = "box, fillcolor=red, style=filled";
}
else if (node instanceof LocalFunctionDeclarationInstruction) {
shape = "Mcircle";
}
else if (node instanceof SubroutineEnterInstruction || node instanceof SubroutineExitInstruction) {
shape = "roundrect, style=rounded";
}
if (!remainedAfterPostProcessInstructions.contains(node)) {
shape += "box, fillcolor=grey, style=filled";
}
out.println(name + "[label=\"" + text + "\", shape=" + shape + "];");
}
}
private static void printEdge(PrintStream out, String from, String to, String label) {
if (label != null) {
label = "[label=\"" + label + "\"]";
}
else {
label = "";
}
out.println(from + " -> " + to + label + ";");
}
}