421 lines
17 KiB
Java
421 lines
17 KiB
Java
/*
|
|
* @author max
|
|
*/
|
|
package org.jetbrains.jet.cfg;
|
|
|
|
import com.intellij.openapi.util.io.FileUtil;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import junit.framework.Test;
|
|
import junit.framework.TestSuite;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.jet.JetLiteFixture;
|
|
import org.jetbrains.jet.JetTestCaseBuilder;
|
|
import org.jetbrains.jet.lang.cfg.LoopInfo;
|
|
import org.jetbrains.jet.lang.cfg.pseudocode.*;
|
|
import org.jetbrains.jet.lang.psi.*;
|
|
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.PrintStream;
|
|
import java.util.*;
|
|
|
|
public class JetControlFlowTest extends JetLiteFixture {
|
|
static {
|
|
System.setProperty("idea.platform.prefix", "Idea");
|
|
}
|
|
|
|
private String myName;
|
|
|
|
public JetControlFlowTest(String dataPath, String name) {
|
|
super(dataPath);
|
|
myName = name;
|
|
}
|
|
|
|
@Override
|
|
public String getName() {
|
|
return "test" + myName;
|
|
}
|
|
|
|
protected String getTestFilePath() {
|
|
return myFullDataPath + "/" + myName;
|
|
}
|
|
|
|
@Override
|
|
protected void runTest() throws Throwable {
|
|
JetFile file = loadPsiFile(myName + ".jet");
|
|
|
|
final Map<JetElement, Pseudocode> data = new LinkedHashMap<JetElement, Pseudocode>();
|
|
final JetPseudocodeTrace pseudocodeTrace = new JetPseudocodeTrace() {
|
|
|
|
@Override
|
|
public void recordControlFlowData(@NotNull JetElement element, @NotNull Pseudocode pseudocode) {
|
|
data.put(element, pseudocode);
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
for (Pseudocode pseudocode : data.values()) {
|
|
pseudocode.postProcess();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void recordLoopInfo(JetExpression expression, LoopInfo blockInfo) {
|
|
}
|
|
|
|
@Override
|
|
public void recordRepresentativeInstruction(@NotNull JetElement element, @NotNull Instruction instruction) {
|
|
}
|
|
|
|
};
|
|
|
|
AnalyzerFacade.analyzeNamespace(file.getRootNamespace(), new JetControlFlowDataTraceFactory() {
|
|
@NotNull
|
|
@Override
|
|
public JetPseudocodeTrace createTrace(JetElement element) {
|
|
return pseudocodeTrace;
|
|
}
|
|
});
|
|
|
|
try {
|
|
processCFData(myName, data);
|
|
}
|
|
catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
finally {
|
|
if ("true".equals(System.getProperty("jet.control.flow.test.dump.graphs"))) {
|
|
dumpDot(myName, data.values());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void processCFData(String name, Map<JetElement, Pseudocode> data) throws IOException {
|
|
Collection<Pseudocode> pseudocodes = data.values();
|
|
|
|
StringBuilder instructionDump = new StringBuilder();
|
|
int i = 0;
|
|
for (Pseudocode pseudocode : pseudocodes) {
|
|
JetElement correspondingElement = pseudocode.getCorrespondingElement();
|
|
String label = "";
|
|
assert (correspondingElement instanceof JetNamedDeclaration || correspondingElement instanceof JetConstructor) :
|
|
"Unexpected element class is pseudocode: " + correspondingElement.getClass();
|
|
if (correspondingElement instanceof JetFunctionLiteral) {
|
|
label = "anonymous_" + i++;
|
|
}
|
|
else if (correspondingElement instanceof JetNamedDeclaration) {
|
|
JetNamedDeclaration namedDeclaration = (JetNamedDeclaration) correspondingElement;
|
|
label = namedDeclaration.getName();
|
|
}
|
|
else {
|
|
label = "this";
|
|
}
|
|
|
|
instructionDump.append("== ").append(label).append(" ==\n");
|
|
|
|
instructionDump.append(correspondingElement.getText());
|
|
instructionDump.append("\n---------------------\n");
|
|
dumpInstructions(pseudocode, instructionDump);
|
|
instructionDump.append("=====================\n");
|
|
|
|
//check edges directions
|
|
Collection<Instruction> instructions = pseudocode.getInstructions();
|
|
for (Instruction instruction : instructions) {
|
|
if (!instruction.isDead()) {
|
|
for (Instruction nextInstruction : instruction.getNextInstructions()) {
|
|
assertTrue("instruction '" + instruction + "' has '" + nextInstruction + "' among next instructions list, but not vice versa",
|
|
nextInstruction.getPreviousInstructions().contains(instruction));
|
|
}
|
|
for (Instruction prevInstruction : instruction.getPreviousInstructions()) {
|
|
assertTrue("instruction '" + instruction + "' has '" + prevInstruction + "' among previous instructions list, but not vice versa",
|
|
prevInstruction.getNextInstructions().contains(instruction));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
String expectedInstructionsFileName = getTestFilePath() + ".instructions";
|
|
File expectedInstructionsFile = new File(expectedInstructionsFileName);
|
|
if (!expectedInstructionsFile.exists()) {
|
|
FileUtil.writeToFile(expectedInstructionsFile, instructionDump.toString());
|
|
fail("No expected instructions for " + name + " generated result is written into " + expectedInstructionsFileName);
|
|
}
|
|
String expectedInstructions = StringUtil.convertLineSeparators(FileUtil.loadFile(expectedInstructionsFile));
|
|
|
|
assertEquals(expectedInstructions, instructionDump.toString());
|
|
|
|
// StringBuilder graphDump = new StringBuilder();
|
|
// for (Pseudocode pseudocode : pseudocodes) {
|
|
// topOrderDump(pseudocode.)
|
|
// }
|
|
}
|
|
|
|
public void dfsDump(Pseudocode pseudocode, StringBuilder nodes, StringBuilder edges, Map<Instruction, String> nodeNames) {
|
|
dfsDump(nodes, edges, pseudocode.getInstructions().get(0), nodeNames);
|
|
}
|
|
|
|
private void dfsDump(StringBuilder nodes, StringBuilder edges, Instruction instruction, Map<Instruction, String> nodeNames) {
|
|
if (nodeNames.containsKey(instruction)) return;
|
|
String name = "n" + nodeNames.size();
|
|
nodeNames.put(instruction, name);
|
|
nodes.append(name).append(" := ").append(renderName(instruction));
|
|
|
|
}
|
|
|
|
private String renderName(Instruction instruction) {
|
|
throw new UnsupportedOperationException(); // TODO
|
|
}
|
|
|
|
private static String formatInstruction(Instruction instruction, int maxLength) {
|
|
String[] parts = instruction.toString().split("\n");
|
|
String prefix = instruction.isDead() ? "* " : " ";
|
|
if (parts.length == 1) {
|
|
return prefix + String.format("%1$-" + maxLength + "s", instruction);
|
|
}
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0, partsLength = parts.length; i < partsLength; i++) {
|
|
String part = parts[i];
|
|
sb.append(prefix).append(String.format("%1$-" + maxLength + "s", part));
|
|
if (i < partsLength - 1) sb.append("\n");
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
private static String formatInstructionList(Collection<Instruction> instructions) {
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append('[');
|
|
for (Iterator<Instruction> iterator = instructions.iterator(); iterator.hasNext(); ) {
|
|
Instruction instruction = iterator.next();
|
|
String instructionText = instruction.toString();
|
|
String[] parts = instructionText.split("\n");
|
|
if (parts.length > 1) {
|
|
StringBuilder instructionSb = new StringBuilder();
|
|
for (String part : parts) {
|
|
instructionSb.append(part.trim()).append(' ');
|
|
}
|
|
if (instructionSb.toString().length() > 30) {
|
|
sb.append(instructionSb.substring(0, 28)).append("..)");
|
|
}
|
|
else {
|
|
sb.append(instructionSb);
|
|
}
|
|
}
|
|
else {
|
|
sb.append(instruction);
|
|
}
|
|
if (iterator.hasNext()) {
|
|
sb.append(", ");
|
|
}
|
|
}
|
|
sb.append(']');
|
|
return sb.toString();
|
|
}
|
|
|
|
public void dumpInstructions(Pseudocode pseudocode, @NotNull StringBuilder out) {
|
|
List<Instruction> instructions = pseudocode.getInstructions();
|
|
List<Pseudocode.PseudocodeLabel> labels = pseudocode.getLabels();
|
|
List<Pseudocode> locals = new ArrayList<Pseudocode>();
|
|
int maxLength = 0;
|
|
int maxNextLength = 0;
|
|
for (Instruction instruction : instructions) {
|
|
String instuctionText = instruction.toString();
|
|
if (instuctionText.length() > maxLength) {
|
|
String[] parts = instuctionText.split("\n");
|
|
if (parts.length > 1) {
|
|
for (String part : parts) {
|
|
if (part.length() > maxLength) {
|
|
maxLength = part.length();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (instuctionText.length() > maxLength) {
|
|
maxLength = instuctionText.length();
|
|
}
|
|
}
|
|
}
|
|
String instructionListText = formatInstructionList(instruction.getNextInstructions());
|
|
if (instructionListText.length() > maxNextLength) {
|
|
maxNextLength = instructionListText.length();
|
|
}
|
|
}
|
|
for (int i = 0, instructionsSize = instructions.size(); i < instructionsSize; i++) {
|
|
Instruction instruction = instructions.get(i);
|
|
if (instruction instanceof LocalDeclarationInstruction) {
|
|
LocalDeclarationInstruction localDeclarationInstruction = (LocalDeclarationInstruction) instruction;
|
|
locals.add(localDeclarationInstruction.getBody());
|
|
}
|
|
for (Pseudocode.PseudocodeLabel label: labels) {
|
|
if (label.getTargetInstructionIndex() == i) {
|
|
out.append(label.getName()).append(":\n");
|
|
}
|
|
}
|
|
|
|
out.append(formatInstruction(instruction, maxLength)).
|
|
append(" NEXT:").append(String.format("%1$-" + maxNextLength + "s", formatInstructionList(instruction.getNextInstructions()))).
|
|
append(" PREV:").append(formatInstructionList(instruction.getPreviousInstructions())).append("\n");
|
|
}
|
|
for (Pseudocode local : locals) {
|
|
dumpInstructions(local, out);
|
|
}
|
|
}
|
|
|
|
public void dumpEdges(List<Instruction> instructions, final PrintStream out, final int[] count, final Map<Instruction, String> nodeToName) {
|
|
for (final Instruction fromInst : instructions) {
|
|
fromInst.accept(new InstructionVisitor() {
|
|
@Override
|
|
public void visitLocalDeclarationInstruction(LocalDeclarationInstruction 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().getInstructions().get(0)), null);
|
|
visitInstructionWithNext(instruction);
|
|
}
|
|
|
|
@Override
|
|
public void visitUnconditionalJump(UnconditionalJumpInstruction instruction) {
|
|
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getResolvedTarget()), null);
|
|
}
|
|
|
|
@Override
|
|
public void visitJump(AbstractJumpInstruction instruction) {
|
|
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getResolvedTarget()), null);
|
|
}
|
|
|
|
@Override
|
|
public void visitNondeterministicJump(NondeterministicJumpInstruction instruction) {
|
|
for (Instruction nextInstruction : instruction.getNextInstructions()) {
|
|
printEdge(out, nodeToName.get(instruction), nodeToName.get(nextInstruction), null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitReturnValue(ReturnValueInstruction instruction) {
|
|
super.visitReturnValue(instruction);
|
|
}
|
|
|
|
@Override
|
|
public void visitReturnNoValue(ReturnNoValueInstruction instruction) {
|
|
super.visitReturnNoValue(instruction);
|
|
}
|
|
|
|
@Override
|
|
public void visitConditionalJump(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(InstructionWithNext instruction) {
|
|
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getNext()), null);
|
|
}
|
|
|
|
@Override
|
|
public void visitSubroutineExit(SubroutineExitInstruction instruction) {
|
|
if (!instruction.getNextInstructions().isEmpty()) {
|
|
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getNextInstructions().iterator().next()), null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitSubroutineSink(SubroutineSinkInstruction instruction) {
|
|
// Nothing
|
|
}
|
|
|
|
@Override
|
|
public void visitInstruction(Instruction instruction) {
|
|
throw new UnsupportedOperationException(instruction.toString());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public void dumpNodes(List<Instruction> instructions, PrintStream out, int[] count, Map<Instruction, String> nodeToName) {
|
|
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 UnsupportedElementInstruction) {
|
|
shape = "box, fillcolor=red, style=filled";
|
|
}
|
|
else if (node instanceof LocalDeclarationInstruction) {
|
|
shape = "Mcircle";
|
|
}
|
|
else if (node instanceof SubroutineEnterInstruction || node instanceof SubroutineExitInstruction) {
|
|
shape = "roundrect, style=rounded";
|
|
}
|
|
out.println(name + "[label=\"" + text + "\", shape=" + shape + "];");
|
|
}
|
|
}
|
|
|
|
private void printEdge(PrintStream out, String from, String to, String label) {
|
|
if (label != null) {
|
|
label = "[label=\"" + label + "\"]";
|
|
}
|
|
else {
|
|
label = "";
|
|
}
|
|
out.println(from + " -> " + to + label + ";");
|
|
}
|
|
|
|
private void dumpDot(String name, Collection<Pseudocode> pseudocodes) throws FileNotFoundException {
|
|
String graphFileName = getTestFilePath() + ".dot";
|
|
File target = new File(graphFileName);
|
|
|
|
PrintStream out = new PrintStream(target);
|
|
|
|
out.println("digraph " + name + " {");
|
|
int[] count = new int[1];
|
|
Map<Instruction, String> nodeToName = new HashMap<Instruction, String>();
|
|
for (Pseudocode pseudocode : pseudocodes) {
|
|
dumpNodes(pseudocode.getInstructions(), out, count, nodeToName);
|
|
}
|
|
int i = 0;
|
|
for (Pseudocode pseudocode : pseudocodes) {
|
|
String label;
|
|
JetElement correspondingElement = pseudocode.getCorrespondingElement();
|
|
if (correspondingElement instanceof JetNamedDeclaration) {
|
|
JetNamedDeclaration namedDeclaration = (JetNamedDeclaration) correspondingElement;
|
|
label = namedDeclaration.getName();
|
|
}
|
|
else {
|
|
label = "anonymous_" + i;
|
|
}
|
|
out.println("subgraph cluster_" + i + " {\n" +
|
|
"label=\"" + label + "\";\n" +
|
|
"color=blue;\n");
|
|
dumpEdges(pseudocode.getInstructions(), out, count, nodeToName);
|
|
out.println("}");
|
|
i++;
|
|
}
|
|
out.println("}");
|
|
out.close();
|
|
}
|
|
|
|
public static TestSuite suite() {
|
|
TestSuite suite = new TestSuite();
|
|
suite.addTest(JetTestCaseBuilder.suiteForDirectory(JetTestCaseBuilder.getTestDataPathBase(), "/cfg/", true, new JetTestCaseBuilder.NamedTestFactory() {
|
|
@NotNull
|
|
@Override
|
|
public Test createTest(@NotNull String dataPath, @NotNull String name) {
|
|
return new JetControlFlowTest(dataPath, name);
|
|
}
|
|
}));
|
|
return suite;
|
|
}
|
|
|
|
}
|