commit 74a433f60206990b8a740c48aa017f4284bf20b3
Author: github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com>
Date: Wed Oct 18 19:57:49 2023 +0000
Initial commit
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..5c98b42
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/asm_util_7_1.xml b/.idea/libraries/asm_util_7_1.xml
new file mode 100644
index 0000000..b18bec2
--- /dev/null
+++ b/.idea/libraries/asm_util_7_1.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..49a9bf2
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..389a9be
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/App.xml b/.idea/runConfigurations/App.xml
new file mode 100644
index 0000000..9e8df3d
--- /dev/null
+++ b/.idea/runConfigurations/App.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Disassembler.xml b/.idea/runConfigurations/Disassembler.xml
new file mode 100644
index 0000000..12c11f0
--- /dev/null
+++ b/.idea/runConfigurations/Disassembler.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d
new file mode 100644
index 0000000..e69de29
diff --git a/.idea/sonarlint/issuestore/index.pb b/.idea/sonarlint/issuestore/index.pb
new file mode 100644
index 0000000..841e6b6
--- /dev/null
+++ b/.idea/sonarlint/issuestore/index.pb
@@ -0,0 +1,3 @@
+
+9
+ README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..e96534f
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5de658b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+# Lab 4 - Software Peformance 2023
+
+This is Lab 4 of the **Software Performance** course at USI.
+
+Go to [this Lab on iCorsi](https://www.icorsi.ch/course/view.php?id=16963).
+
+## Submission Info
+
+Property | Value
+------------ | -------------
+First Name | ...
+Last Name | ...
+
+## Submission Checklist
+
+Please complete this checklist (turn [ ] into [X]) before you submit:
+
+- [ ] I completed the above Submission Info
+- [ ] I built the project in IntelliJ (Build > Build Project)
+- [ ] I (re)implemented the ControlFlowGraphBuilder (copy from Lab 3)
+- [ ] I (re)implemented the ControlFlowGraphRenderer (copy from Lab 3)
+- [ ] I implemented the Traversal
+- [ ] I implemented the DominatorAnalyzer
+- [ ] I wrote the source code myself and did not look at the source code of my class mates
+- [ ] I ran all the JUnit tests and verified that they all pass
+- [ ] I manually checked that my implementation is correct by doing this:
+ - [ ] I studied the test-input/ExampleClass.java source code
+ - [ ] I ran App to produce the dot files (in test-output/)
+ - [ ] I ran dot to turn the dot files in test-output into a PDF
+ - [ ] I manually verified that the PDF contains a set of pages per method of ExampleClass
+ - [ ] I manually verified that the CFGs in the PDFs correspond to the methods' source code
+ - [ ] I manually verified that the dominator trees in the PDFs correspond to the method's source code
+- [ ] I committed my changes (at least one commit, but possibly many)
+- [ ] I pushed my commits to GitHub
diff --git a/lib/asm-7.1-javadoc.jar b/lib/asm-7.1-javadoc.jar
new file mode 100644
index 0000000..aa69a1b
Binary files /dev/null and b/lib/asm-7.1-javadoc.jar differ
diff --git a/lib/asm-7.1-sources.jar b/lib/asm-7.1-sources.jar
new file mode 100644
index 0000000..48f8aba
Binary files /dev/null and b/lib/asm-7.1-sources.jar differ
diff --git a/lib/asm-7.1.jar b/lib/asm-7.1.jar
new file mode 100644
index 0000000..355eb08
Binary files /dev/null and b/lib/asm-7.1.jar differ
diff --git a/lib/asm-tree-7.1-javadoc.jar b/lib/asm-tree-7.1-javadoc.jar
new file mode 100644
index 0000000..3a75f80
Binary files /dev/null and b/lib/asm-tree-7.1-javadoc.jar differ
diff --git a/lib/asm-tree-7.1-sources.jar b/lib/asm-tree-7.1-sources.jar
new file mode 100644
index 0000000..df27a98
Binary files /dev/null and b/lib/asm-tree-7.1-sources.jar differ
diff --git a/lib/asm-tree-7.1.jar b/lib/asm-tree-7.1.jar
new file mode 100644
index 0000000..8e9ddbd
Binary files /dev/null and b/lib/asm-tree-7.1.jar differ
diff --git a/lib/asm-util-7.1-javadoc.jar b/lib/asm-util-7.1-javadoc.jar
new file mode 100644
index 0000000..b4c7605
Binary files /dev/null and b/lib/asm-util-7.1-javadoc.jar differ
diff --git a/lib/asm-util-7.1-sources.jar b/lib/asm-util-7.1-sources.jar
new file mode 100644
index 0000000..499579e
Binary files /dev/null and b/lib/asm-util-7.1-sources.jar differ
diff --git a/lib/asm-util-7.1.jar b/lib/asm-util-7.1.jar
new file mode 100644
index 0000000..5e61f2c
Binary files /dev/null and b/lib/asm-util-7.1.jar differ
diff --git a/src/ch/usi/inf/sp/bytecode/Disassembler.java b/src/ch/usi/inf/sp/bytecode/Disassembler.java
new file mode 100644
index 0000000..36747a6
--- /dev/null
+++ b/src/ch/usi/inf/sp/bytecode/Disassembler.java
@@ -0,0 +1,265 @@
+package ch.usi.inf.sp.bytecode;
+
+
+import java.io.FileInputStream;
+import java.util.List;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.*;
+import org.objectweb.asm.util.Printer;
+
+
+/**
+ * A Disassembler can disassemble Java class files.
+ * It presents an output similar to javap -c.
+ * Given the name of the class file as a command line argument,
+ * it prints the name of the class, a list of all methods,
+ * and for each method, the list of all Java bytecode instructions.
+ *
+ * The format of the disassembled bytecode includes the opcodes
+ * (in the form of mnemonics such as "ILOAD") and all the operands.
+ * Some operands can be printed as simple integers, while others have to be printed in a more understandable form
+ * (e.g. method or field names and descriptors).
+ * Operands of branch instructions are shown as an "id" of the targeted instruction.
+ * For this, all instructions of a method, including ASM's pseudo-instructions (LABEL, LINE, FRAME),
+ * are numbered, starting at 0.
+ * The instruction id allows you to look up the corresponding instruction object in the instruction list:
+ * AbstractInsnNode target = instructionList.get(targetId);
+ *
methods = clazz.methods;
+ for (int m = 0; m < methods.size(); m++) {
+ final MethodNode method = methods.get(m);
+ sb.append(disassembleMethod(method));
+ }
+
+ return sb.toString();
+ }
+
+ public static String disassembleMethod(final MethodNode method) {
+ final StringBuffer sb = new StringBuffer(" Method: ");
+ sb.append(method.name);
+ sb.append(method.desc);
+ sb.append('\n');
+ // get the list of all instructions in that method
+ final InsnList instructions = method.instructions;
+ for (int i = 0; i < instructions.size(); i++) {
+ final AbstractInsnNode instruction = instructions.get(i);
+ sb.append(" ");
+ sb.append(Disassembler.disassembleInstruction(instruction, i, instructions));
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Hint: Check out org.objectweb.asm.MethodVisitor to determine which instructions (opcodes)
+ * have which instruction types (subclasses of AbstractInsnNode).
+ *
+ * @see org.objectweb.asm.MethodVisitor
+ *
+ * E.g. the comment in org.objectweb.asm.MethodVisitor.visitIntInsn(int opcode, int operand)
+ * shows the list of all opcodes that are represented as instructions of type IntInsnNode.
+ * That list e.g. includes the BIPUSH opcode.
+ */
+ public static String disassembleInstruction(final AbstractInsnNode instruction, final int i, final InsnList instructions) {
+ StringBuffer sb = new StringBuffer();
+ final int opcode = instruction.getOpcode();
+ final String mnemonic = opcode == -1 ? "" : Printer.OPCODES[instruction.getOpcode()];
+ sb.append(i + ":\t" + mnemonic + " ");
+ // There are different subclasses of AbstractInsnNode.
+ // AbstractInsnNode.getType() represents the subclass as an int.
+ // Note:
+ // to check the subclass of an instruction node, we can either use:
+ // if (instruction.getType()==AbstractInsnNode.LABEL)
+ // or we can use:
+ // if (instruction instanceof LabelNode)
+ // They give the same result, but the first one can be used in a switch statement.
+ switch (instruction.getType()) {
+ case AbstractInsnNode.LABEL:
+ // pseudo-instruction (branch or exception target)
+ sb.append("// label");
+ break;
+ case AbstractInsnNode.FRAME:
+ // pseudo-instruction (stack frame map)
+ sb.append("// stack frame map");
+ break;
+ case AbstractInsnNode.LINE:
+ // pseudo-instruction (line number information)
+ sb.append("// line number information");
+ case AbstractInsnNode.INSN:
+ // Opcodes: NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2,
+ // ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0,
+ // FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD,
+ // DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE,
+ // DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP,
+ // DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD,
+ // DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV,
+ // FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL,
+ // LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR,
+ // I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B,
+ // I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN,
+ // FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW,
+ // MONITORENTER, or MONITOREXIT.
+ // zero operands, nothing to print
+ break;
+ case AbstractInsnNode.INT_INSN:
+ // Opcodes: NEWARRAY, BIPUSH, SIPUSH.
+ if (instruction.getOpcode() == Opcodes.NEWARRAY) {
+ // NEWARRAY
+ sb.append(Printer.TYPES[((IntInsnNode) instruction).operand]);
+ } else {
+ // BIPUSH or SIPUSH
+ sb.append(((IntInsnNode) instruction).operand);
+ }
+ break;
+ case AbstractInsnNode.JUMP_INSN:
+ // Opcodes: IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ,
+ // IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ,
+ // IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
+ {
+ final LabelNode targetInstruction = ((JumpInsnNode) instruction).label;
+ final int targetId = instructions.indexOf(targetInstruction);
+ sb.append(targetId);
+ break;
+ }
+ case AbstractInsnNode.LDC_INSN:
+ // Opcodes: LDC.
+ sb.append(((LdcInsnNode) instruction).cst);
+ break;
+ case AbstractInsnNode.IINC_INSN:
+ // Opcodes: IINC.
+ sb.append(((IincInsnNode) instruction).var);
+ sb.append(" ");
+ sb.append(((IincInsnNode) instruction).incr);
+ break;
+ case AbstractInsnNode.TYPE_INSN:
+ // Opcodes: NEW, ANEWARRAY, CHECKCAST or INSTANCEOF.
+ sb.append(((TypeInsnNode) instruction).desc);
+ break;
+ case AbstractInsnNode.VAR_INSN:
+ // Opcodes: ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE,
+ // LSTORE, FSTORE, DSTORE, ASTORE or RET.
+ sb.append(((VarInsnNode) instruction).var);
+ break;
+ case AbstractInsnNode.FIELD_INSN:
+ // Opcodes: GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
+ sb.append(((FieldInsnNode) instruction).owner);
+ sb.append(".");
+ sb.append(((FieldInsnNode) instruction).name);
+ sb.append(" ");
+ sb.append(((FieldInsnNode) instruction).desc);
+ break;
+ case AbstractInsnNode.METHOD_INSN:
+ // Opcodes: INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC,
+ // INVOKEINTERFACE or INVOKEDYNAMIC.
+ sb.append(((MethodInsnNode) instruction).owner);
+ sb.append(".");
+ sb.append(((MethodInsnNode) instruction).name);
+ sb.append(" ");
+ sb.append(((MethodInsnNode) instruction).desc);
+ break;
+ case AbstractInsnNode.MULTIANEWARRAY_INSN:
+ // Opcodes: MULTIANEWARRAY.
+ sb.append(((MultiANewArrayInsnNode) instruction).desc);
+ sb.append(" ");
+ sb.append(((MultiANewArrayInsnNode) instruction).dims);
+ break;
+ case AbstractInsnNode.LOOKUPSWITCH_INSN:
+ // Opcodes: LOOKUPSWITCH.
+ {
+ final List keys = ((LookupSwitchInsnNode) instruction).keys;
+ final List labels = ((LookupSwitchInsnNode) instruction).labels;
+ for (int t = 0; t < keys.size(); t++) {
+ final int key = (Integer) keys.get(t);
+ final LabelNode targetInstruction = (LabelNode) labels.get(t);
+ final int targetId = instructions.indexOf(targetInstruction);
+ sb.append(key + ": " + targetId + ", ");
+ }
+ final LabelNode defaultTargetInstruction = ((LookupSwitchInsnNode) instruction).dflt;
+ final int defaultTargetId = instructions.indexOf(defaultTargetInstruction);
+ sb.append("default: " + defaultTargetId);
+ break;
+ }
+ case AbstractInsnNode.TABLESWITCH_INSN:
+ // Opcodes: TABLESWITCH.
+ {
+ final int minKey = ((TableSwitchInsnNode) instruction).min;
+ final List labels = ((TableSwitchInsnNode) instruction).labels;
+ for (int t = 0; t < labels.size(); t++) {
+ final int key = minKey + t;
+ final LabelNode targetInstruction = (LabelNode) labels.get(t);
+ final int targetId = instructions.indexOf(targetInstruction);
+ sb.append(key + ": " + targetId + ", ");
+ }
+ final LabelNode defaultTargetInstruction = ((TableSwitchInsnNode) instruction).dflt;
+ final int defaultTargetId = instructions.indexOf(defaultTargetInstruction);
+ sb.append("default: " + defaultTargetId);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/cfg/App.java b/src/ch/usi/inf/sp/cfg/App.java
new file mode 100644
index 0000000..34da338
--- /dev/null
+++ b/src/ch/usi/inf/sp/cfg/App.java
@@ -0,0 +1,99 @@
+package ch.usi.inf.sp.cfg;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+
+import ch.usi.inf.sp.bytecode.Disassembler;
+import ch.usi.inf.sp.dom.DominatorAnalyzer;
+import ch.usi.inf.sp.dom.DominatorTree;
+import ch.usi.inf.sp.dom.DominatorTreeRenderer;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+
+
+public final class App {
+
+ /**
+ * Invoke like this...
+ *
+ * java App test-input/java10/ExampleClass.class test-output
+ *
+ * to produce one disassembly, one CFG, one dominator tree,
+ * and one combined graph (CFG with additional dotted dominance edges)
+ * for each method in ExampleClass.
+ * Afterwards, go to the test-output folder, and call...
+ *
+ * dot -Tpdf -oall.pdf *.dot
+ *
+ * ...to produce a file all.pdf containing one page for each graph, or...
+ *
+ * dot -Tpdf -oall.combined.pdf *.combined.pdf
+ *
+ * ...to produce a file all.combined.pdf with just the combined graphs.
+ *
+ * MAKE SURE YOU MANUALLY VERIFY FOR EACH METHOD THAT THE DOMINATORS ARE CORRECT.
+ */
+ public static void main(final String[] args) throws IOException {
+ final File classFile = new File(args[0]);
+ final File outputDirectory = new File(args[1]);
+ final App app = new App(classFile, outputDirectory);
+ app.execute();
+ }
+
+ private final File classFile;
+ private final File outputDirectory;
+
+
+ public App(final File classFile, final File outputDirectory) {
+ this.classFile = classFile;
+ this.outputDirectory = outputDirectory;
+ }
+
+ /**
+ * Save the given contents into a file with the given fileName in the outputDirectory.
+ */
+ private void save(final String fileName, final String contents) throws IOException {
+ if (!outputDirectory.exists()) {
+ outputDirectory.mkdirs();
+ }
+ final File file = new File(outputDirectory, fileName);
+ final FileWriter writer = new FileWriter(file);
+ writer.write(contents);
+ writer.close();
+ }
+
+ public void execute() throws IOException {
+ final ClassReader cr = new ClassReader(new FileInputStream(classFile));
+ // create an empty ClassNode (in-memory representation of a class)
+ final ClassNode cn = new ClassNode();
+ // have the ClassReader read the class file and populate the ClassNode with the corresponding information
+ cr.accept(cn, 0);
+ // disassemble and perform control-flow analysis
+ processClass(cn);
+ }
+
+ private void processClass(final ClassNode cn) throws IOException {
+ System.out.println("Class: " + cn.name);
+ // get the list of all methods in that class
+ final List methods = cn.methods;
+ for (int m = 0; m < methods.size(); m++) {
+ final MethodNode method = methods.get(m);
+ processMethod(method);
+ }
+ }
+
+ private void processMethod(final MethodNode method) throws IOException {
+ System.out.println(" Method: " + method.name + method.desc);
+ save(method.name + ".asm.txt", Disassembler.disassembleMethod(method));
+ final ControlFlowGraph cfg = ControlFlowGraphBuilder.createControlFlowGraph(method);
+ save(method.name + ".cfg.dot", ControlFlowGraphRenderer.renderControlFlowGraph(method.name, cfg));
+ final DominatorTree dt = DominatorAnalyzer.analyze(cfg);
+ save(method.name + ".dt.dot", DominatorTreeRenderer.renderDominatorTree(method.name, dt));
+ save(method.name + ".combined.dot", DominatorTreeRenderer.renderCombined(method.name, cfg, dt));
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/cfg/BasicBlock.java b/src/ch/usi/inf/sp/cfg/BasicBlock.java
new file mode 100644
index 0000000..f3be384
--- /dev/null
+++ b/src/ch/usi/inf/sp/cfg/BasicBlock.java
@@ -0,0 +1,44 @@
+package ch.usi.inf.sp.cfg;
+
+import ch.usi.inf.sp.graph.Node;
+
+import java.util.ArrayList;
+
+
+public final class BasicBlock extends Node {
+
+ private final int id;
+ private final ArrayList instructions;
+
+
+ public BasicBlock(final int id) {
+ this.id = id;
+ instructions = new ArrayList();
+ }
+
+ public void appendInstruction(final String s) {
+ instructions.add(s);
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public int getInstructionCount() {
+ return instructions.size();
+ }
+
+ public String getInstruction(int i) {
+ return instructions.get(i);
+ }
+
+ public Iterable getInstructions() {
+ return instructions;
+ }
+
+ @Override
+ public String toString() {
+ return "\""+id+"\"";
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/cfg/ControlFlowEdge.java b/src/ch/usi/inf/sp/cfg/ControlFlowEdge.java
new file mode 100644
index 0000000..a9aea2d
--- /dev/null
+++ b/src/ch/usi/inf/sp/cfg/ControlFlowEdge.java
@@ -0,0 +1,19 @@
+package ch.usi.inf.sp.cfg;
+
+
+import ch.usi.inf.sp.graph.Edge;
+
+public final class ControlFlowEdge extends Edge {
+
+ private final String label;
+
+
+ public ControlFlowEdge(final String label) {
+ this.label = label;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/cfg/ControlFlowGraph.java b/src/ch/usi/inf/sp/cfg/ControlFlowGraph.java
new file mode 100644
index 0000000..df92c96
--- /dev/null
+++ b/src/ch/usi/inf/sp/cfg/ControlFlowGraph.java
@@ -0,0 +1,110 @@
+package ch.usi.inf.sp.cfg;
+
+import ch.usi.inf.sp.graph.DiGraph;
+
+
+public final class ControlFlowGraph extends DiGraph {
+
+ private BasicBlock entry;
+ private BasicBlock exit;
+
+
+ public ControlFlowGraph() {
+ entry = new BasicBlock(-1);
+ addNode(entry);
+ exit = new BasicBlock(-2);
+ addNode(exit);
+ }
+
+ private final void checkContains(BasicBlock block, String name) {
+ if (!getNodes().contains(block)) {
+ throw new IllegalStateException("Control flow graph does not contain the given " + name + " block.");
+ }
+ }
+
+ public ControlFlowEdge addEntryEdge(BasicBlock firstBlock) {
+ if (entry.getOutEdges().size() > 0) {
+ throw new IllegalStateException("Control flow graph already has an entry edge. It can only have one.");
+ }
+ checkContains(firstBlock, "firstBlock");
+ ControlFlowEdge edge = new ControlFlowEdge("");
+ addEdge(edge);
+ connect(entry, edge, firstBlock);
+ return edge;
+ }
+
+ public ControlFlowEdge addExitEdge(BasicBlock returnBlock) {
+ checkContains(returnBlock, "returnBlock");
+ ControlFlowEdge edge = new ControlFlowEdge("");
+ addEdge(edge);
+ connect(returnBlock, edge, exit);
+ return edge;
+ }
+
+ public ControlFlowEdge addFallthroughEdge(BasicBlock fromBlock, BasicBlock toBlock) {
+ checkContains(fromBlock, "fromBlock");
+ checkContains(toBlock, "toBlock");
+ ControlFlowEdge edge = new ControlFlowEdge("");
+ addEdge(edge);
+ connect(fromBlock, edge, toBlock);
+ return edge;
+ }
+
+ public ControlFlowEdge addBranchTakenEdge(BasicBlock fromBlock, BasicBlock toBlock) {
+ checkContains(fromBlock, "fromBlock");
+ checkContains(toBlock, "toBlock");
+ ControlFlowEdge edge = new ControlFlowEdge("T");
+ addEdge(edge);
+ connect(fromBlock, edge, toBlock);
+ return edge;
+ }
+
+ public ControlFlowEdge addCaseEdge(BasicBlock fromBlock, BasicBlock toBlock, int key) {
+ checkContains(fromBlock, "fromBlock");
+ checkContains(toBlock, "toBlock");
+ ControlFlowEdge edge = new ControlFlowEdge("" + key);
+ addEdge(edge);
+ connect(fromBlock, edge, toBlock);
+ return edge;
+ }
+
+ public ControlFlowEdge addDefaultEdge(BasicBlock fromBlock, BasicBlock toBlock) {
+ checkContains(fromBlock, "fromBlock");
+ checkContains(toBlock, "toBlock");
+ ControlFlowEdge edge = new ControlFlowEdge("default");
+ addEdge(edge);
+ connect(fromBlock, edge, toBlock);
+ return edge;
+ }
+
+ public BasicBlock getEntry() {
+ return entry;
+ }
+
+ public BasicBlock getExit() {
+ return exit;
+ }
+
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("digraph CFG {\n");
+ for (final BasicBlock node : getNodes()) {
+ if (node==entry) {
+ sb.append(" " + node + " [shape=circle,style=filled,label=e]\n");
+ } else if (node==exit) {
+ sb.append(" " + node + " [shape=circle,style=filled,label=x]\n");
+ } else {
+ sb.append(" " + node + " [shape=rectangle]\n");
+ }
+ }
+ for (final ControlFlowEdge edge : getEdges()) {
+ if (edge.getLabel().length()>0) {
+ sb.append(" " + edge + " [label=" + edge.getLabel() + "]\n");
+ } else {
+ sb.append(" " + edge + "\n");
+ }
+ }
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/cfg/ControlFlowGraphBuilder.java b/src/ch/usi/inf/sp/cfg/ControlFlowGraphBuilder.java
new file mode 100644
index 0000000..9579528
--- /dev/null
+++ b/src/ch/usi/inf/sp/cfg/ControlFlowGraphBuilder.java
@@ -0,0 +1,23 @@
+package ch.usi.inf.sp.cfg;
+
+import java.util.List;
+
+import ch.usi.inf.sp.bytecode.Disassembler;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.JumpInsnNode;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LookupSwitchInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TableSwitchInsnNode;
+
+
+public class ControlFlowGraphBuilder {
+
+ public static ControlFlowGraph createControlFlowGraph(final MethodNode method) {
+ //TODO
+ return null;
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/cfg/ControlFlowGraphRenderer.java b/src/ch/usi/inf/sp/cfg/ControlFlowGraphRenderer.java
new file mode 100644
index 0000000..4ec24b7
--- /dev/null
+++ b/src/ch/usi/inf/sp/cfg/ControlFlowGraphRenderer.java
@@ -0,0 +1,11 @@
+package ch.usi.inf.sp.cfg;
+
+
+public class ControlFlowGraphRenderer {
+
+ public static String renderControlFlowGraph(final String label, final ControlFlowGraph cfg) {
+ //TODO
+ return null;
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/dom/DominanceEdge.java b/src/ch/usi/inf/sp/dom/DominanceEdge.java
new file mode 100644
index 0000000..df2720f
--- /dev/null
+++ b/src/ch/usi/inf/sp/dom/DominanceEdge.java
@@ -0,0 +1,8 @@
+package ch.usi.inf.sp.dom;
+
+import ch.usi.inf.sp.graph.Edge;
+
+
+public class DominanceEdge extends Edge {
+
+}
diff --git a/src/ch/usi/inf/sp/dom/DominatorAnalyzer.java b/src/ch/usi/inf/sp/dom/DominatorAnalyzer.java
new file mode 100644
index 0000000..adbc072
--- /dev/null
+++ b/src/ch/usi/inf/sp/dom/DominatorAnalyzer.java
@@ -0,0 +1,52 @@
+package ch.usi.inf.sp.dom;
+
+import ch.usi.inf.sp.cfg.BasicBlock;
+import ch.usi.inf.sp.cfg.ControlFlowGraph;
+import ch.usi.inf.sp.graph.Traversal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+public class DominatorAnalyzer {
+
+ /**
+ * Cooper et al.'s "Engineered Algorithm".
+ * ================================================================
+ * for all nodes, b // initialize the dominators array
+ * doms[b] ← Undefined
+ * doms[entryNode] ← entryNode
+ * Changed ← true
+ * while (Changed)
+ * Changed ← false
+ * for all nodes, b, in reverse postorder (except entryNode)
+ * newidom ← first (processed) predecessor of b // (pick one)
+ * for all other predecessors, p, of b
+ * if doms[p] != Undefined // i.e., if doms[p] already calculated
+ * newidom ← intersect(p, newidom)
+ * if doms[b] != newidom
+ * doms[b] ← newidom
+ * Changed ← true
+ *
+ * function intersect(b1, b2) returns node
+ * finger1 ← b1
+ * finger2 ← b2
+ * while (finger1 != finger2)
+ * while (finger1 < finger2)
+ * finger1 = doms[finger1]
+ * while (finger2 < finger1)
+ * finger2 = doms[finger2]
+ * return finger1
+ * ================================================================
+ * Figure 3 of Cooper, Harvey, Kennedy
+ */
+ public static DominatorTree analyze(final ControlFlowGraph cfg) {
+ //TODO
+ return null;
+ }
+
+ // probably add a method intersect(...)
+
+}
diff --git a/src/ch/usi/inf/sp/dom/DominatorTree.java b/src/ch/usi/inf/sp/dom/DominatorTree.java
new file mode 100644
index 0000000..9d97d01
--- /dev/null
+++ b/src/ch/usi/inf/sp/dom/DominatorTree.java
@@ -0,0 +1,80 @@
+package ch.usi.inf.sp.dom;
+
+import ch.usi.inf.sp.cfg.BasicBlock;
+import ch.usi.inf.sp.graph.DiGraph;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+public final class DominatorTree extends DiGraph {
+
+ private Map nodeForBlock;
+ private DominatorTreeNode rootNode;
+
+
+ public DominatorTree() {
+ nodeForBlock = new HashMap<>();
+ }
+
+ public DominatorTreeNode getNodeForBlock(final BasicBlock block) {
+ return nodeForBlock.get(block);
+ }
+
+ public DominatorTreeNode setRootBlock(BasicBlock rootBlock) {
+ DominatorTreeNode newRootNode = nodeForBlock.get(rootBlock);
+ if (newRootNode==null) {
+ newRootNode = new DominatorTreeNode(rootBlock);
+ nodeForBlock.put(rootBlock, newRootNode);
+ }
+ if (!getNodes().contains(newRootNode)) {
+ addNode(newRootNode);
+ }
+ rootNode = newRootNode;
+ return rootNode;
+ }
+
+ public DominatorTreeNode getRoot() {
+ return rootNode;
+ }
+
+ public DominanceEdge addDominanceEdge(BasicBlock idom, BasicBlock child) {
+ DominatorTreeNode idomNode = nodeForBlock.get(idom);
+ if (idomNode==null) {
+ idomNode = new DominatorTreeNode(idom);
+ nodeForBlock.put(idom, idomNode);
+ }
+ if (!getNodes().contains(idomNode)) {
+ addNode(idomNode);
+ }
+ DominatorTreeNode childNode = nodeForBlock.get(child);
+ if (childNode==null) {
+ childNode = new DominatorTreeNode(child);
+ nodeForBlock.put(child, childNode);
+ }
+ if (!getNodes().contains(childNode)) {
+ addNode(childNode);
+ }
+ final DominanceEdge edge = new DominanceEdge();
+ addEdge(edge);
+ connect(idomNode, edge, childNode);
+ return edge;
+ }
+
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("digraph DOM {\n");
+ for (final DominatorTreeNode node : getNodes()) {
+ if (node==rootNode) {
+ sb.append(" " + node + " [style=filled]\n");
+ } else {
+ sb.append(" " + node + "\n");
+ }
+ }
+ for (final DominanceEdge edge : getEdges()) {
+ sb.append(" "+edge+"\n");
+ }
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/dom/DominatorTreeNode.java b/src/ch/usi/inf/sp/dom/DominatorTreeNode.java
new file mode 100644
index 0000000..5a02c69
--- /dev/null
+++ b/src/ch/usi/inf/sp/dom/DominatorTreeNode.java
@@ -0,0 +1,30 @@
+package ch.usi.inf.sp.dom;
+
+import ch.usi.inf.sp.cfg.BasicBlock;
+import ch.usi.inf.sp.graph.Node;
+
+
+public class DominatorTreeNode extends Node {
+
+ private final BasicBlock block;
+
+
+ public DominatorTreeNode(final BasicBlock block) {
+ this.block = block;
+ }
+
+ public BasicBlock getBlock() {
+ return block;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof DominatorTreeNode && block==((DominatorTreeNode)other).block;
+ }
+
+ @Override
+ public String toString() {
+ return "\"D("+block.getId()+")\"";
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/dom/DominatorTreeRenderer.java b/src/ch/usi/inf/sp/dom/DominatorTreeRenderer.java
new file mode 100644
index 0000000..34b2cd8
--- /dev/null
+++ b/src/ch/usi/inf/sp/dom/DominatorTreeRenderer.java
@@ -0,0 +1,59 @@
+package ch.usi.inf.sp.dom;
+
+
+import ch.usi.inf.sp.cfg.BasicBlock;
+import ch.usi.inf.sp.cfg.ControlFlowEdge;
+import ch.usi.inf.sp.cfg.ControlFlowGraph;
+
+public class DominatorTreeRenderer {
+
+ public static String renderDominatorTree(final String label, final DominatorTree tree) {
+ final StringBuffer sb = new StringBuffer();
+ sb.append("digraph dominatorTree {\n");
+ sb.append(" label=\"" + label + "\"\n");
+ for (final DominatorTreeNode node : tree.getNodes()) {
+ if (node == tree.getRoot()) {
+ sb.append(" " + node.toString() + " [style=filled]\n");
+ } else {
+ sb.append(" " + node.toString() + "\n");
+ }
+ }
+ for (final DominanceEdge edge : tree.getEdges()) {
+ sb.append(" " + edge.getFrom().toString() + " -> " + edge.getTo().toString()+"\n");
+ }
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ public static String renderCombined(final String label, final ControlFlowGraph cfg, final DominatorTree dt) {
+ final StringBuffer sb = new StringBuffer();
+ sb.append("digraph combined {\n");
+ sb.append(" label=\"" + label + "\"\n");
+ // render basic blocks
+ for (final BasicBlock block : cfg.getNodes()) {
+ if (block == cfg.getEntry()) {
+ sb.append(" " + block.getId() + " [shape=record,style=filled,label=\""+block.getId()+"|entry\"]\n");
+ } else if (block == cfg.getExit()) {
+ sb.append(" " + block.getId() + " [shape=record,label=\""+block.getId()+"|exit\"]\n");
+ } else {
+ sb.append(" " + block.getId() + " [shape=record,label=\""+block.getId()+"|{");
+ // use \l to tell dot to left-align each line
+ sb.append(String.join("\\l|", block.getInstructions()));
+ sb.append("\\l}\"]\n");
+ }
+ }
+ // render control flow edges (solid)
+ for (final ControlFlowEdge edge : cfg.getEdges()) {
+ sb.append(" " + edge.getFrom().getId() + " -> " + edge.getTo().getId());
+ sb.append(" [label=\"" + edge.getLabel() + "\"]\n");
+ }
+ // render dominance edges (dotted)
+ for (final DominanceEdge edge : dt.getEdges()) {
+ sb.append(" " + edge.getFrom().getBlock().getId() + " -> " + edge.getTo().getBlock().getId()+" [style=dotted]\n");
+ }
+
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/graph/DiGraph.java b/src/ch/usi/inf/sp/graph/DiGraph.java
new file mode 100644
index 0000000..2e92d55
--- /dev/null
+++ b/src/ch/usi/inf/sp/graph/DiGraph.java
@@ -0,0 +1,69 @@
+package ch.usi.inf.sp.graph;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+public class DiGraph, E extends Edge> {
+
+ private final ArrayList nodes;
+ private final ArrayList edges;
+
+
+ public DiGraph() {
+ nodes = new ArrayList();
+ edges = new ArrayList();
+ }
+
+ public void addNode(N node) {
+ nodes.add(node);
+ }
+
+ public void addEdge(E edge) {
+ edges.add(edge);
+ }
+
+ public void connect(N from, E edge, N to) {
+ if (!nodes.contains(from)) {
+ throw new IllegalStateException("Graph does not contain from node.");
+ }
+ if (!nodes.contains(to)) {
+ throw new IllegalStateException("Graph does not contain to node.");
+ }
+ if (!edges.contains(edge)) {
+ throw new IllegalStateException("Graph does not contain edge.");
+ }
+ if (from.getOutEdges().contains(edge)) {
+ throw new IllegalStateException("From node already has this edge as out edge");
+ }
+ if (to.getInEdges().contains(edge)) {
+ throw new IllegalStateException("To node already has this edge as in edge");
+ }
+ edge.setFrom(from);
+ edge.setTo(to);
+ from.addOutEdge(edge);
+ to.addInEdge(edge);
+ }
+
+ public List getEdges() {
+ return Collections.unmodifiableList(edges);
+ }
+
+ public List getNodes() {
+ return Collections.unmodifiableList(nodes);
+ }
+
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("digraph G {\n");
+ for (final Node node : nodes) {
+ sb.append(" "+node+"\n");
+ }
+ for (final Edge edge : edges) {
+ sb.append(" "+edge+"\n");
+ }
+ sb.append("}\n");
+ return sb.toString();
+ }
+}
diff --git a/src/ch/usi/inf/sp/graph/Edge.java b/src/ch/usi/inf/sp/graph/Edge.java
new file mode 100644
index 0000000..bbbefd7
--- /dev/null
+++ b/src/ch/usi/inf/sp/graph/Edge.java
@@ -0,0 +1,32 @@
+package ch.usi.inf.sp.graph;
+
+
+public class Edge {
+
+ private N from;
+ private N to;
+
+ public Edge() {
+ }
+
+ public void setFrom(N node) {
+ from = node;
+ }
+
+ public N getFrom() {
+ return from;
+ }
+
+ public void setTo(N node) {
+ to = node;
+ }
+
+ public N getTo() {
+ return to;
+ }
+
+ public String toString() {
+ return from.toString()+" -> "+to.toString();
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/graph/Node.java b/src/ch/usi/inf/sp/graph/Node.java
new file mode 100644
index 0000000..3373d1f
--- /dev/null
+++ b/src/ch/usi/inf/sp/graph/Node.java
@@ -0,0 +1,38 @@
+package ch.usi.inf.sp.graph;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+public class Node {
+
+ private final ArrayList inEdges;
+ private final ArrayList outEdges;
+
+ public Node() {
+ inEdges = new ArrayList();
+ outEdges = new ArrayList();
+ }
+
+ public void addInEdge(E edge) {
+ inEdges.add(edge);
+ }
+
+ public void addOutEdge(E edge) {
+ outEdges.add(edge);
+ }
+
+ public List getInEdges() {
+ return Collections.unmodifiableList(inEdges);
+ }
+
+ public List getOutEdges() {
+ return Collections.unmodifiableList(outEdges);
+ }
+
+ public String toString() {
+ return '"'+super.toString()+'"';
+ }
+
+}
diff --git a/src/ch/usi/inf/sp/graph/Traversal.java b/src/ch/usi/inf/sp/graph/Traversal.java
new file mode 100644
index 0000000..ed5f1b1
--- /dev/null
+++ b/src/ch/usi/inf/sp/graph/Traversal.java
@@ -0,0 +1,39 @@
+package ch.usi.inf.sp.graph;
+
+import java.util.*;
+
+
+public class Traversal {
+
+ public static , N extends Node, E extends Edge>
+ List getNodesInReversePostOrder(final DiGraph graph, final N entryNode) {
+ final List orderedNodes = getNodesInPostOrder(graph, entryNode);
+ Collections.reverse(orderedNodes);
+ return orderedNodes;
+ }
+
+ /**
+ * From: https://eli.thegreenplace.net/2015/directed-graph-traversal-orderings-and-applications-to-data-flow-analysis/
+ *
+ * def postorder(graph, root):
+ * """Return a post-order ordering of nodes in the graph."""
+ * visited = set()
+ * order = []
+ * def dfs_walk(node):
+ * visited.add(node)
+ * for succ in graph.successors(node):
+ * if not succ in visited:
+ * dfs_walk(succ)
+ * order.append(node)
+ * dfs_walk(root)
+ * return order
+ */
+ public static , N extends Node, E extends Edge>
+ List getNodesInPostOrder(final DiGraph graph, final N entryNode) {
+ //TODO
+ return null;
+ }
+
+ // probably add a method dfsWalk(...)
+
+}
diff --git a/starter-lab-04-dominator-analysis.iml b/starter-lab-04-dominator-analysis.iml
new file mode 100644
index 0000000..622d288
--- /dev/null
+++ b/starter-lab-04-dominator-analysis.iml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test-input/ExampleClass.java b/test-input/ExampleClass.java
new file mode 100644
index 0000000..2153d5e
--- /dev/null
+++ b/test-input/ExampleClass.java
@@ -0,0 +1,288 @@
+import java.util.Set;
+
+
+/**
+ * This class contains a set of methods that are useful for testing
+ * Java disassemblers and control-flow graph generators.
+ *
+ * @author Matthias.Hauswirth@usi.ch
+ */
+public class ExampleClass {
+
+ public void emptyMethod() {
+ return;
+ }
+
+ //--- conditionals
+ public int ifMethod(int i) {
+ int j = 0;
+ if (i<0) {
+ j = 1;
+ }
+ return j;
+ }
+
+ public int ifElseMethod(int i) {
+ int j = 0;
+ if (i>0) {
+ j = 0;
+ } else {
+ j = i;
+ }
+ return j;
+ }
+
+ public int switchMethod(int i) {
+ int j = 0;
+ switch (i) {
+ case 0: j = 0; break;
+ case 1: j = 1; break;
+ case 2: j = 2; break;
+ default: j = -1;
+ }
+ return j;
+ }
+
+ public int switchMethod2(int i) {
+ int j = 0;
+ switch (i) {
+ case 0: j = 0; break;
+ case 1000: j = 1; break;
+ case 2000: j = 2; break;
+ default: j = -1;
+ }
+ return j;
+ }
+
+
+
+ //--- loops
+ public int forMethod(int i) {
+ int sum = 0;
+ for (int j=0; j0) {
+ sum +=i;
+ i--;
+ }
+ return sum;
+ }
+
+ public int doWhileMethod(int i) {
+ int sum = 0;
+ do {
+ sum += i;
+ i--;
+ } while (i>0);
+ return sum;
+ }
+
+ public int forEachArrayMethod(String[] a) {
+ int sum = 0;
+ for (String s : a) {
+ sum++;
+ }
+ return sum;
+ }
+
+ public int forEachCollectionMethod(Set a) {
+ int sum = 0;
+ for (String s : a) {
+ sum++;
+ }
+ return sum;
+ }
+
+ public int forWithBreakMethod(int n) {
+ int sum = 0;
+ for (int i=0; ib?a:b;
+ }
+
+ public int shortCircuitMethod(int i, int j, int k) {
+ if (i>j && ij & i it = bb.getInstructions().iterator();
+ assertEquals("i1", it.next());
+ assertEquals("i2", it.next());
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/cfg/ControlFlowEdgeTest.java b/test/ch/usi/inf/sp/cfg/ControlFlowEdgeTest.java
new file mode 100644
index 0000000..f55727f
--- /dev/null
+++ b/test/ch/usi/inf/sp/cfg/ControlFlowEdgeTest.java
@@ -0,0 +1,15 @@
+package ch.usi.inf.sp.cfg;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ControlFlowEdgeTest {
+
+ @Test
+ void newControlFlowGraphEdge() {
+ ControlFlowEdge e = new ControlFlowEdge("e1");
+ assertEquals("e1", e.getLabel());
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/cfg/ControlFlowGraphBuilderTest.java b/test/ch/usi/inf/sp/cfg/ControlFlowGraphBuilderTest.java
new file mode 100644
index 0000000..724c853
--- /dev/null
+++ b/test/ch/usi/inf/sp/cfg/ControlFlowGraphBuilderTest.java
@@ -0,0 +1,84 @@
+package ch.usi.inf.sp.cfg;
+
+import ch.usi.inf.sp.bytecode.Disassembler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * This tests the ControlFlowGraphBuilder by running it on the compiled ExampleClass.
+ * It loads the ExampleClass.class (as compiled by Java10's javac).
+ * The different test methods test the CFGB on different inputs
+ * (different methods in class ExampleClass).
+ * The test is VERY WEAK.
+ * That is, there are many bugs it will not discover.
+ */
+class ControlFlowGraphBuilderTest {
+
+ private static ClassNode classNode;
+
+ private static MethodNode getMethod(String name, String desc) {
+ for (MethodNode methodNode : classNode.methods) {
+ if (methodNode.name.equals(name) && methodNode.desc.equals(desc)) {
+ return methodNode;
+ }
+ }
+ throw new IllegalArgumentException("Error in test harness: method "+name+" not found!");
+ }
+
+ @BeforeEach
+ void setUp() throws IOException {
+ // Use ASM to read a class file (from test-input) and create a ClassNode
+ File classFile = new File("test-input/java10/ExampleClass.class");
+ final ClassReader cr = new ClassReader(new FileInputStream(classFile));
+ // create an empty ClassNode (in-memory representation of a class)
+ classNode = new ClassNode();
+ // have the ClassReader read the class file and populate the ClassNode with the corresponding information
+ cr.accept(classNode, 0);
+ }
+
+ @Test
+ void createControlFlowGraphOfEmptyMethod() {
+ MethodNode methodNode = getMethod("emptyMethod", "()V");
+ ControlFlowGraph g = ControlFlowGraphBuilder.createControlFlowGraph(methodNode);
+ assertEquals(3, g.getNodes().size(), "CFG should contain three basic blocks: entry, exit, and one with code");
+ BasicBlock bb = ((ControlFlowEdge)g.getEntry().getOutEdges().get(0)).getTo();
+ final InsnList asmInstructions = methodNode.instructions;
+ assertEquals(asmInstructions.size(), bb.getInstructionCount(), "basic block's instruction count differs from number of instructions in method node");
+ for (int i = 0; i < asmInstructions.size(); i++) {
+ AbstractInsnNode asmInstruction = asmInstructions.get(i);
+ String asmInstructionAsString = Disassembler.disassembleInstruction(asmInstruction, i, asmInstructions);
+ String bbInstruction = bb.getInstruction(i);
+ assertEquals(asmInstructionAsString, bbInstruction, "basic block's instruction string differs from disassembled ASM instruction");
+ //System.out.println(asmInstructionAsString+" == "+bbInstruction);
+ }
+ }
+
+ @Test
+ void createControlFlowGraphOfIfMethod() {
+ MethodNode methodNode = getMethod("ifMethod", "(I)I");
+ ControlFlowGraph g = ControlFlowGraphBuilder.createControlFlowGraph(methodNode);
+ assertEquals(5, g.getNodes().size(), "CFG should contain five basic blocks: entry, exit, before if, then, after if");
+ }
+
+ @Test
+ void createControlFlowGraphOfIfElseMethod() {
+ MethodNode methodNode = getMethod("ifElseMethod", "(I)I");
+ ControlFlowGraph g = ControlFlowGraphBuilder.createControlFlowGraph(methodNode);
+ assertEquals(6, g.getNodes().size(), "CFG should contain five basic blocks: entry, exit, before if, then, else, after if");
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/cfg/ControlFlowGraphRendererTest.java b/test/ch/usi/inf/sp/cfg/ControlFlowGraphRendererTest.java
new file mode 100644
index 0000000..a10c78e
--- /dev/null
+++ b/test/ch/usi/inf/sp/cfg/ControlFlowGraphRendererTest.java
@@ -0,0 +1,98 @@
+package ch.usi.inf.sp.cfg;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+
+/**
+ * Tests ControlFlowGraphRenderer by
+ * asking it to render simple, artificially generated CFGs.
+ * The test is VERY WEAK.
+ * That is, there are many bugs it will not discover.
+ */
+class ControlFlowGraphRendererTest {
+
+ @Test
+ public void renderEmptyControlFlowGraph() {
+ ControlFlowGraph cfg = new ControlFlowGraph();
+ String result = ControlFlowGraphRenderer.renderControlFlowGraph("THELABEL", cfg);
+ //System.out.println(result);
+ assertTrue(result.contains("THELABEL"), "must include the given label");
+ assertTrue(result.startsWith("digraph "));
+ assertTrue(result.contains("{"));
+ assertTrue(result.contains("}"));
+ assertTrue(result.contains("["), "must set properties of entry/exit nodes");
+ assertTrue(result.contains("]"), "must set properties of entry/exit nodes");
+ assertTrue(result.contains("label"), "must set label for graph, and for nodes");
+ assertTrue(result.contains("-1"), "must contain entry node's ID: -1");
+ assertTrue(result.contains("-2"), "must contain exit node's ID: -2");
+ assertTrue(result.contains("shape"), "must set shape for entry/exit nodes");
+ }
+
+ @Test
+ public void renderSingleBlockControlFlowGraph() {
+ ControlFlowGraph cfg = new ControlFlowGraph();
+ BasicBlock bb = new BasicBlock(99);
+ bb.appendInstruction("THEINSTRUCTION");
+ cfg.addNode(bb);
+ cfg.addEntryEdge(bb);
+ cfg.addExitEdge(bb);
+ String result = ControlFlowGraphRenderer.renderControlFlowGraph("THELABEL", cfg);
+ //System.out.println(result);
+ assertTrue(result.contains("THELABEL"), "must include the given label");
+ assertTrue(result.startsWith("digraph "));
+ assertTrue(result.contains("{"));
+ assertTrue(result.contains("}"));
+ assertTrue(result.contains("["), "must set properties of entry/exit nodes");
+ assertTrue(result.contains("]"), "must set properties of entry/exit nodes");
+ assertTrue(result.contains("label"), "must set label for graph, and for nodes");
+ assertTrue(result.contains("-1"), "must contain entry node's ID: -1");
+ assertTrue(result.contains("-2"), "must contain exit node's ID: -2");
+ assertTrue(result.contains("shape"), "must set shape for entry/exit nodes");
+
+ assertTrue(result.contains("->"), "must contain an edge (->)");
+ assertTrue(result.contains("99"), "must include the basic block's ID (99)");
+ assertTrue(result.contains("THEINSTRUCTION"), "must contain THEINSTRUCTION in the label of the basic block");
+ assertTrue(result.contains("record"), "must use a record shape for the basic block");
+ }
+
+
+ @Test
+ public void renderTwoBlockControlFlowGraph() {
+ ControlFlowGraph cfg = new ControlFlowGraph();
+ BasicBlock bb1 = new BasicBlock(11);
+ bb1.appendInstruction("I11.1");
+ bb1.appendInstruction("I11.2");
+ cfg.addNode(bb1);
+ BasicBlock bb2 = new BasicBlock(22);
+ bb2.appendInstruction("I22.1");
+ bb2.appendInstruction("I22.2");
+ cfg.addNode(bb2);
+ cfg.addEntryEdge(bb1);
+ cfg.addFallthroughEdge(bb1, bb2);
+ cfg.addExitEdge(bb2);
+ String result = ControlFlowGraphRenderer.renderControlFlowGraph("THELABEL", cfg);
+ //System.out.println(result);
+ assertTrue(result.contains("THELABEL"), "must include the given label");
+ assertTrue(result.startsWith("digraph "));
+ assertTrue(result.contains("{"));
+ assertTrue(result.contains("}"));
+ assertTrue(result.contains("["), "must set properties of entry/exit nodes");
+ assertTrue(result.contains("]"), "must set properties of entry/exit nodes");
+ assertTrue(result.contains("label"), "must set label for graph, and for nodes");
+ assertTrue(result.contains("-1"), "must contain entry node's ID: -1");
+ assertTrue(result.contains("-2"), "must contain exit node's ID: -2");
+ assertTrue(result.contains("shape"), "must set shape for entry/exit nodes");
+
+ assertTrue(result.contains("->"), "must contain an edge (->)");
+ assertTrue(result.contains("11"), "must include the first basic block's ID (11)");
+ assertTrue(result.contains("22"), "must include the second basic block's ID (22)");
+ assertTrue(result.contains("I11.1"), "must contain instruction I11.1 in the label of the first basic block");
+ assertTrue(result.contains("I11.2"), "must contain instruction I11.2 in the label of the first basic block");
+ assertTrue(result.contains("I22.1"), "must contain instruction I22.1 in the label of the first basic block");
+ assertTrue(result.contains("I22.2"), "must contain instruction I22.2 in the label of the first basic block");
+ assertTrue(result.contains("record"), "must use a record shape for the basic block");
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/cfg/ControlFlowGraphTest.java b/test/ch/usi/inf/sp/cfg/ControlFlowGraphTest.java
new file mode 100644
index 0000000..a98f2bb
--- /dev/null
+++ b/test/ch/usi/inf/sp/cfg/ControlFlowGraphTest.java
@@ -0,0 +1,171 @@
+package ch.usi.inf.sp.cfg;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ControlFlowGraphTest {
+
+ private final void assertConnected(BasicBlock from, ControlFlowEdge e, BasicBlock to) {
+ assertTrue(from.getOutEdges().contains(e));
+ assertTrue(to.getInEdges().contains(e));
+ assertSame(from, e.getFrom());
+ assertSame(to, e.getTo());
+ }
+
+ @Test
+ void newControlFlowGraph() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ assertNotNull(g.getEntry());
+ assertNotNull(g.getExit());
+ assertNotSame(g.getEntry(), g.getExit());
+ assertEquals(2, g.getNodes().size());
+ assertTrue(g.getNodes().contains(g.getEntry()));
+ assertTrue(g.getNodes().contains(g.getExit()));
+ assertEquals(0, g.getEdges().size());
+ }
+
+ @Test
+ void addEntryEdge() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock bb = new BasicBlock(1);
+ g.addNode(bb);
+ ControlFlowEdge e = g.addEntryEdge(bb);
+ assertNotNull(e);
+ assertEquals(3, g.getNodes().size());
+ assertTrue(g.getNodes().contains(bb));
+ assertEquals(1, g.getEdges().size());
+ assertTrue(g.getEdges().contains(g.getEntry().getOutEdges().get(0)));
+ assertSame(bb, g.getEntry().getOutEdges().get(0).getTo());
+ }
+
+ @Test
+ void addEntryEdgeToNonContainedBasicBlock() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock bb = new BasicBlock(1);
+ assertThrows(IllegalStateException.class, () -> g.addEntryEdge(bb));
+ }
+
+ @Test
+ void addTwoEntryEdges() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock bb = new BasicBlock(1);
+ g.addNode(bb);
+ g.addEntryEdge(bb);
+ assertThrows(IllegalStateException.class, () -> g.addEntryEdge(bb));
+ }
+
+ @Test
+ void addExitEdge() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock bb = new BasicBlock(1);
+ g.addNode(bb);
+ ControlFlowEdge e = g.addExitEdge(bb);
+ assertNotNull(e);
+ assertEquals(3, g.getNodes().size());
+ assertTrue(g.getNodes().contains(bb));
+ assertEquals(1, g.getEdges().size());
+ assertTrue(g.getEdges().contains(g.getExit().getInEdges().get(0)));
+ assertSame(bb, g.getExit().getInEdges().get(0).getFrom());
+ }
+
+ @Test
+ void addExitEdgeFromNonContainedBasicBlock() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock bb = new BasicBlock(1);
+ assertThrows(IllegalStateException.class, () -> g.addExitEdge(bb));
+ }
+
+ @Test
+ void addTwoExitEdges() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock bb = new BasicBlock(1);
+ g.addNode(bb);
+ g.addExitEdge(bb);
+ g.addExitEdge(bb);
+ assertEquals(3, g.getNodes().size());
+ assertTrue(g.getNodes().contains(bb));
+ assertEquals(2, g.getEdges().size());
+ assertTrue(g.getEdges().contains(g.getExit().getInEdges().get(0)));
+ assertTrue(g.getEdges().contains(g.getExit().getInEdges().get(1)));
+ assertSame(bb, g.getExit().getInEdges().get(0).getFrom());
+ assertSame(bb, g.getExit().getInEdges().get(1).getFrom());
+ }
+
+ @Test
+ void addFallthroughEdge() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock from = new BasicBlock(1);
+ BasicBlock to = new BasicBlock(2);
+ g.addNode(from);
+ g.addNode(to);
+ ControlFlowEdge e = g.addFallthroughEdge(from, to);
+ assertNotNull(e);
+ assertEquals("", e.getLabel());
+ assertEquals(4, g.getNodes().size());
+ assertTrue(g.getNodes().contains(from));
+ assertTrue(g.getNodes().contains(to));
+ assertEquals(1, g.getEdges().size());
+ assertTrue(g.getEdges().contains(e));
+ assertConnected(from, e, to);
+ }
+
+ @Test
+ void addBranchTakenEdge() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock from = new BasicBlock(1);
+ BasicBlock to = new BasicBlock(2);
+ g.addNode(from);
+ g.addNode(to);
+ ControlFlowEdge e = g.addBranchTakenEdge(from, to);
+ assertNotNull(e);
+ assertEquals("T", e.getLabel());
+ assertEquals(4, g.getNodes().size());
+ assertTrue(g.getNodes().contains(from));
+ assertTrue(g.getNodes().contains(to));
+ assertEquals(1, g.getEdges().size());
+ assertTrue(g.getEdges().contains(e));
+ assertConnected(from, e, to);
+ }
+
+ @Test
+ void addCaseEdge() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock from = new BasicBlock(1);
+ BasicBlock to = new BasicBlock(2);
+ g.addNode(from);
+ g.addNode(to);
+ ControlFlowEdge e = g.addCaseEdge(from, to, 99);
+ assertNotNull(e);
+ assertEquals("99", e.getLabel());
+ assertEquals(4, g.getNodes().size());
+ assertTrue(g.getNodes().contains(from));
+ assertTrue(g.getNodes().contains(to));
+ assertEquals(1, g.getEdges().size());
+ assertTrue(g.getEdges().contains(e));
+ assertConnected(from, e, to);
+ }
+
+ @Test
+ void addDefaultEdge() {
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock from = new BasicBlock(1);
+ BasicBlock to = new BasicBlock(2);
+ g.addNode(from);
+ g.addNode(to);
+ ControlFlowEdge e = g.addDefaultEdge(from, to);
+ assertNotNull(e);
+ assertEquals("default", e.getLabel());
+ assertEquals(4, g.getNodes().size());
+ assertTrue(g.getNodes().contains(from));
+ assertTrue(g.getNodes().contains(to));
+ assertEquals(1, g.getEdges().size());
+ assertTrue(g.getEdges().contains(e));
+ assertConnected(from, e, to);
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/dom/DominatorAnalyzerTest.java b/test/ch/usi/inf/sp/dom/DominatorAnalyzerTest.java
new file mode 100644
index 0000000..2c9c5cb
--- /dev/null
+++ b/test/ch/usi/inf/sp/dom/DominatorAnalyzerTest.java
@@ -0,0 +1,208 @@
+package ch.usi.inf.sp.dom;
+
+import ch.usi.inf.sp.cfg.BasicBlock;
+import ch.usi.inf.sp.cfg.ControlFlowGraph;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static ch.usi.inf.sp.dom.DominatorTreeAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+class DominatorAnalyzerTest {
+
+ @Test
+ void analyzeEntryExit() {
+ ControlFlowGraph cfg = new ControlFlowGraph();
+ cfg.addFallthroughEdge(cfg.getEntry(), cfg.getExit());
+ System.out.println(cfg);
+
+ DominatorTree t = DominatorAnalyzer.analyze(cfg);
+ System.out.println(t);
+ assertAllNodesMapToTheirBlocks(t);
+ assertNodesHaveCorrectNumberOfParents(t);
+ assertChildrenPointToParent(t);
+ assertPathToRoot(t, Arrays.asList(cfg.getExit(), cfg.getEntry()));
+ assertSame(cfg.getEntry(), t.getRoot().getBlock(), "Root must hold entry block");
+ assertEquals(1, t.getRoot().getOutEdges().size(), "Root must have one out edge");
+ assertTrue(t.getRoot().getOutEdges().stream().map(e->e.getTo()).anyMatch(d -> d==t.getNodeForBlock(cfg.getExit())), "Root must have child holding exit block");
+ }
+
+ @Test
+ void analyzeEntryNodeExit() {
+ ControlFlowGraph cfg = new ControlFlowGraph();
+ BasicBlock b0 = new BasicBlock(0);
+ cfg.addNode(b0);
+ cfg.addFallthroughEdge(cfg.getEntry(), b0);
+ cfg.addFallthroughEdge(b0, cfg.getExit());
+ System.out.println(cfg);
+
+ DominatorTree t = DominatorAnalyzer.analyze(cfg);
+ System.out.println(t);
+ assertAllNodesMapToTheirBlocks(t);
+ assertNodesHaveCorrectNumberOfParents(t);
+ assertChildrenPointToParent(t);
+ assertSame(cfg.getEntry(), t.getRoot().getBlock(), "Root must hold entry block");
+ assertEquals(1, t.getRoot().getOutEdges().size(), "Root must have one out edge");
+ assertSame(b0, t.getRoot().getOutEdges().get(0).getTo().getBlock(), "Root must have child holding b0 block");
+ assertEquals(1, t.getRoot().getOutEdges().get(0).getTo().getOutEdges().size(), "Root must have child that has one out edge");
+ assertSame(cfg.getExit(), t.getRoot().getOutEdges().get(0).getTo().getOutEdges().get(0).getTo().getBlock(), "Root must have child that has child holding exit block");
+ }
+
+ @Test
+ void analyzeIfThen() {
+ ControlFlowGraph cfg = new ControlFlowGraph();
+ BasicBlock bIf = new BasicBlock(0);
+ cfg.addNode(bIf);
+ BasicBlock bThen = new BasicBlock(1);
+ cfg.addNode(bThen);
+ cfg.addFallthroughEdge(cfg.getEntry(), bIf);
+ cfg.addFallthroughEdge(bIf, cfg.getExit());
+ cfg.addBranchTakenEdge(bIf, bThen);
+ cfg.addFallthroughEdge(bThen, cfg.getExit());
+ System.out.println(cfg);
+
+ DominatorTree t = DominatorAnalyzer.analyze(cfg);
+ System.out.println(t);
+ assertAllNodesMapToTheirBlocks(t);
+ assertNodesHaveCorrectNumberOfParents(t);
+ assertChildrenPointToParent(t);
+ assertSame(cfg.getEntry(), t.getRoot().getBlock(), "Root must hold entry block");
+ assertEquals(1, t.getRoot().getOutEdges().size(), "Root must have one out edge");
+ assertSame(bIf, t.getRoot().getOutEdges().get(0).getTo().getBlock(), "Root must have child holding bIf block");
+ assertEquals(2, t.getRoot().getOutEdges().get(0).getTo().getOutEdges().size(), "Root must have child that has two out edges");
+ assertTrue(t.getRoot().getOutEdges().get(0).getTo().getOutEdges().stream().map(e->e.getTo()).anyMatch(d->d.getBlock()==bThen), "Root must have child that has child holding bThen block");
+ assertTrue(t.getRoot().getOutEdges().get(0).getTo().getOutEdges().stream().map(e->e.getTo()).anyMatch(d->d.getBlock()==cfg.getExit()), "Root must have child that has child holding exit block");
+ assertEquals(0, t.getNodeForBlock(bThen).getOutEdges().size());
+ }
+
+ @Test
+ void analyzeIfThenElse() {
+ ControlFlowGraph cfg = new ControlFlowGraph();
+ BasicBlock bIf = new BasicBlock(0);
+ cfg.addNode(bIf);
+ BasicBlock bThen = new BasicBlock(1);
+ cfg.addNode(bThen);
+ BasicBlock bElse = new BasicBlock(2);
+ cfg.addNode(bElse);
+ cfg.addFallthroughEdge(cfg.getEntry(), bIf);
+ cfg.addBranchTakenEdge(bIf, bElse);
+ cfg.addFallthroughEdge(bIf, bThen);
+ cfg.addFallthroughEdge(bElse, cfg.getExit());
+ cfg.addFallthroughEdge(bThen, cfg.getExit());
+ System.out.println(cfg);
+
+ DominatorTree t = DominatorAnalyzer.analyze(cfg);
+ System.out.println(t);
+ assertAllNodesMapToTheirBlocks(t);
+ assertNodesHaveCorrectNumberOfParents(t);
+ assertChildrenPointToParent(t);
+ assertSame(cfg.getEntry(), t.getRoot().getBlock(), "Root must hold entry block");
+ assertEquals(1, t.getRoot().getOutEdges().size(), "Root must have one out edge");
+ assertSame(bIf, t.getRoot().getOutEdges().get(0).getTo().getBlock(), "Root must have child holding bIf block");
+ assertEquals(3, t.getNodeForBlock(bIf).getOutEdges().size(), "bIf must have three out edges");
+ assertTrue(t.getNodeForBlock(bIf).getOutEdges().stream().map(e->e.getTo()).anyMatch(d->d.getBlock()==bThen), "bIf must have child holding bThen block");
+ assertTrue(t.getNodeForBlock(bIf).getOutEdges().stream().map(e->e.getTo()).anyMatch(d->d.getBlock()==bElse), "bIf must have child holding bElse block");
+ assertTrue(t.getNodeForBlock(bIf).getOutEdges().stream().map(e->e.getTo()).anyMatch(d->d.getBlock()==cfg.getExit()), "bIf must have child holding exit block");
+ assertEquals(0, t.getNodeForBlock(bThen).getOutEdges().size());
+ assertEquals(0, t.getNodeForBlock(bElse).getOutEdges().size());
+ }
+
+ @Test
+ void analyzeLoop() {
+ ControlFlowGraph cfg = new ControlFlowGraph();
+ BasicBlock bHeader = new BasicBlock(0);
+ cfg.addNode(bHeader);
+ BasicBlock bBody = new BasicBlock(1);
+ cfg.addNode(bBody);
+ cfg.addFallthroughEdge(cfg.getEntry(), bHeader);
+ cfg.addBranchTakenEdge(bHeader, bBody);
+ cfg.addFallthroughEdge(bHeader, cfg.getExit());
+ cfg.addFallthroughEdge(bBody, bHeader);
+ System.out.println(cfg);
+
+ DominatorTree t = DominatorAnalyzer.analyze(cfg);
+ System.out.println(t);
+ assertAllNodesMapToTheirBlocks(t);
+ assertNodesHaveCorrectNumberOfParents(t);
+ assertChildrenPointToParent(t);
+ assertSame(cfg.getEntry(), t.getRoot().getBlock(), "Root must hold entry block");
+ assertEquals(1, t.getRoot().getOutEdges().size(), "Root must have one out edge");
+ assertSame(bHeader, t.getRoot().getOutEdges().get(0).getTo().getBlock(), "Root must have child holding bHeader block");
+ assertEquals(2, t.getNodeForBlock(bHeader).getOutEdges().size(), "bHeader must have two out edges");
+ assertTrue(t.getNodeForBlock(bHeader).getOutEdges().stream().map(e->e.getTo()).anyMatch(d->d.getBlock()==bBody), "bHeader must have child holding bBody block");
+ assertTrue(t.getNodeForBlock(bHeader).getOutEdges().stream().map(e->e.getTo()).anyMatch(d->d.getBlock()==cfg.getExit()), "bHeader must have child holding exit block");
+ assertEquals(0, t.getNodeForBlock(bBody).getOutEdges().size(), "bBody must have zero out edges");
+ }
+
+ @Test
+ void analyzeCooperFigure2() {
+ // This is an irreducible graph
+ // (and it doesn't have an exit edge, so we "abuse" the CFG a bit, which is fine)
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock b5 = g.getEntry();
+ BasicBlock b4 = new BasicBlock(4);
+ g.addNode(b4);
+ BasicBlock b3 = new BasicBlock(3);
+ g.addNode(b3);
+ BasicBlock b2 = new BasicBlock(2);
+ g.addNode(b2);
+ BasicBlock b1 = g.getExit();
+ g.addFallthroughEdge(b5, b3);
+ g.addFallthroughEdge(b5, b4);
+ g.addFallthroughEdge(b3, b2);
+ g.addFallthroughEdge(b4, b1);
+ g.addFallthroughEdge(b2, b1);
+ g.addFallthroughEdge(b1, b2);
+ System.out.println(g);
+
+ DominatorTree t = DominatorAnalyzer.analyze(g);
+ System.out.println(t);
+ assertAllNodesMapToTheirBlocks(t);
+ assertNodesHaveCorrectNumberOfParents(t);
+ assertChildrenPointToParent(t);
+ assertPathToRoot(t, Arrays.asList(b4, b5));
+ assertPathToRoot(t, Arrays.asList(b3, b5));
+ assertPathToRoot(t, Arrays.asList(b2, b5));
+ assertPathToRoot(t, Arrays.asList(b1, b5));
+ }
+
+ @Test
+ void analyzeCooperFigure4() {
+ // This is an irreducible graph
+ // (and it doesn't have an exit edge, so we "abuse" the CFG a bit, which is fine)
+ ControlFlowGraph g = new ControlFlowGraph();
+ BasicBlock b6 = g.getEntry();
+ BasicBlock b5 = new BasicBlock(5);
+ g.addNode(b5);
+ BasicBlock b4 = new BasicBlock(4);
+ g.addNode(b4);
+ BasicBlock b3 = new BasicBlock(3);
+ g.addNode(b3);
+ BasicBlock b2 = new BasicBlock(2);
+ g.addNode(b2);
+ BasicBlock b1 = g.getExit();
+ g.addFallthroughEdge(b6, b4);
+ g.addFallthroughEdge(b6, b5);
+ g.addFallthroughEdge(b4, b3);
+ g.addFallthroughEdge(b4, b2);
+ g.addFallthroughEdge(b3, b2);
+ g.addFallthroughEdge(b2, b1);
+ g.addFallthroughEdge(b2, b3);
+ g.addFallthroughEdge(b1, b2);
+ g.addFallthroughEdge(b5, b1);
+ System.out.println(g);
+
+ DominatorTree t = DominatorAnalyzer.analyze(g);
+ System.out.println(t);
+ assertAllNodesMapToTheirBlocks(t);
+ assertNodesHaveCorrectNumberOfParents(t);
+ assertChildrenPointToParent(t);
+ assertPathToRoot(t, Arrays.asList(b5, b6));
+ assertPathToRoot(t, Arrays.asList(b4, b6));
+ assertPathToRoot(t, Arrays.asList(b3, b6));
+ assertPathToRoot(t, Arrays.asList(b2, b6));
+ assertPathToRoot(t, Arrays.asList(b1, b6));
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/dom/DominatorTreeAssertions.java b/test/ch/usi/inf/sp/dom/DominatorTreeAssertions.java
new file mode 100644
index 0000000..cd8e197
--- /dev/null
+++ b/test/ch/usi/inf/sp/dom/DominatorTreeAssertions.java
@@ -0,0 +1,51 @@
+package ch.usi.inf.sp.dom;
+
+import ch.usi.inf.sp.cfg.BasicBlock;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * These are static methods used in unit tests to assert properties on DominatorTrees.
+ */
+public class DominatorTreeAssertions {
+
+ public static void assertAllNodesMapToTheirBlocks(DominatorTree t) {
+ for (DominatorTreeNode n : t.getNodes()) {
+ assertSame(n, t.getNodeForBlock(n.getBlock()));
+ }
+ }
+
+ public static void assertNodesHaveCorrectNumberOfParents(DominatorTree t) {
+ for (DominatorTreeNode node : t.getNodes()) {
+ if (node==t.getRoot()) {
+ assertEquals(0, node.getInEdges().size());
+ } else {
+ assertEquals(1, node.getInEdges().size());
+ }
+ }
+ }
+
+ public static void assertChildrenPointToParent(DominatorTree t) {
+ for (DominatorTreeNode parent : t.getNodes()) {
+ for (DominanceEdge e : parent.getOutEdges()) {
+ DominatorTreeNode child = e.getTo();
+ assertSame(parent, child.getInEdges().get(0).getFrom());
+ }
+ }
+ }
+
+ public static void assertPathToRoot(DominatorTree t, List pathToRoot) {
+ DominatorTreeNode child = null;
+ for (BasicBlock b: pathToRoot) {
+ DominatorTreeNode node = t.getNodeForBlock(b);
+ assertSame(b, node.getBlock());
+ if (child!=null) {
+ assertEquals(b, child.getInEdges().get(0).getFrom().getBlock());
+ }
+ child = node;
+ }
+ }
+
+}
diff --git a/test/ch/usi/inf/sp/dom/DominatorTreeTest.java b/test/ch/usi/inf/sp/dom/DominatorTreeTest.java
new file mode 100644
index 0000000..6b8ac3c
--- /dev/null
+++ b/test/ch/usi/inf/sp/dom/DominatorTreeTest.java
@@ -0,0 +1,41 @@
+package ch.usi.inf.sp.dom;
+
+import ch.usi.inf.sp.cfg.BasicBlock;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DominatorTreeTest {
+
+ @Test
+ void newDominatorTree() {
+ DominatorTree t = new DominatorTree();
+ assertNull(t.getRoot());
+ assertEquals(0, t.getNodes().size());
+ assertEquals(0, t.getEdges().size());
+ }
+
+ @Test
+ void setRoot() {
+ DominatorTree t = new DominatorTree();
+ BasicBlock b = new BasicBlock(0);
+ t.setRootBlock(b);
+ assertSame(b, t.getRoot().getBlock());
+ assertEquals(1, t.getNodes().size());
+ assertSame(b, t.getNodes().get(0).getBlock());
+ assertEquals(0, t.getEdges().size());
+ }
+
+ @Test
+ void addDominanceEdge() {
+ DominatorTree t = new DominatorTree();
+ BasicBlock rootBlock = new BasicBlock(1);
+ BasicBlock childBlock = new BasicBlock(2);
+ t.setRootBlock(rootBlock);
+ DominanceEdge edge = t.addDominanceEdge(rootBlock, childBlock);
+ assertSame(rootBlock, t.getRoot().getBlock());
+ assertEquals(1, t.getRoot().getOutEdges().size());
+ assertSame(edge, t.getRoot().getOutEdges().get(0));
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/graph/DiGraphTest.java b/test/ch/usi/inf/sp/graph/DiGraphTest.java
new file mode 100644
index 0000000..f6ac68e
--- /dev/null
+++ b/test/ch/usi/inf/sp/graph/DiGraphTest.java
@@ -0,0 +1,147 @@
+package ch.usi.inf.sp.graph;
+
+import ch.usi.inf.sp.graph.DiGraph;
+import ch.usi.inf.sp.graph.Edge;
+import ch.usi.inf.sp.graph.Node;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DiGraphTest {
+
+ @Test
+ void newDiGraph() {
+ DiGraph g = new DiGraph();
+ assertEquals(0, g.getNodes().size());
+ assertEquals(0, g.getEdges().size());
+ }
+
+ @Test
+ void toStringEmpty() {
+ DiGraph g = new DiGraph();
+ assertEquals("digraph G {\n}\n", g.toString());
+ }
+
+ @Test
+ void toStringOneEdge() {
+ DiGraph g = new DiGraph();
+ Node n1 = new Node();
+ Node n2 = new Node();
+ Edge e = new Edge();
+ g.addNode(n1);
+ g.addNode(n2);
+ g.addEdge(e);
+ g.connect(n1, e, n2);
+ assertEquals("digraph G {\n"+
+ " "+n1.toString()+"\n"+
+ " "+n2.toString()+"\n"+
+ " "+e.toString()+"\n"+
+ "}\n", g.toString());
+ }
+
+ @Test
+ void addNode() {
+ DiGraph g = new DiGraph();
+ Node n = new Node();
+ g.addNode(n);
+ assertEquals(1, g.getNodes().size());
+ assertSame(n, g.getNodes().get(0));
+ assertEquals(0, g.getEdges().size());
+ }
+
+ @Test
+ void addEdge() {
+ DiGraph g = new DiGraph();
+ Edge e = new Edge();
+ g.addEdge(e);
+ assertEquals(0, g.getNodes().size());
+ assertEquals(1, g.getEdges().size());
+ assertSame(e, g.getEdges().get(0));
+ }
+
+ @Test
+ void connect() {
+ DiGraph g = new DiGraph();
+ Edge e = new Edge();
+ Node n1 = new Node();
+ Node n2 = new Node();
+ g.addNode(n1);
+ g.addNode(n2);
+ g.addEdge(e);
+ g.connect(n1, e, n2);
+ assertEquals(2, g.getNodes().size());
+ assertTrue(g.getNodes().contains(n1));
+ assertTrue(g.getNodes().contains(n2));
+ assertEquals(1, g.getEdges().size());
+ assertSame(e, g.getEdges().get(0));
+ assertSame(e, n1.getOutEdges().get(0));
+ assertSame(e, n2.getInEdges().get(0));
+ assertSame(n1, e.getFrom());
+ assertSame(n2, e.getTo());
+ }
+
+ @Test
+ void connectEdgeNotContained() {
+ DiGraph g = new DiGraph();
+ Edge e = new Edge();
+ Node n1 = new Node();
+ Node n2 = new Node();
+ g.addNode(n1);
+ g.addNode(n2);
+ assertThrows(IllegalStateException.class, () -> g.connect(n1, e, n2));
+ }
+
+ @Test
+ void connectFromNodeNotContained() {
+ DiGraph g = new DiGraph();
+ Edge e = new Edge();
+ Node n1 = new Node();
+ Node n2 = new Node();
+ g.addNode(n2);
+ g.addEdge(e);
+ assertThrows(IllegalStateException.class, () -> g.connect(n1, e, n2));
+ }
+
+ @Test
+ void connectToNodeNotContained() {
+ DiGraph g = new DiGraph();
+ Edge e = new Edge();
+ Node n1 = new Node();
+ Node n2 = new Node();
+ g.addNode(n1);
+ g.addEdge(e);
+ assertThrows(IllegalStateException.class, () -> g.connect(n1, e, n2));
+ }
+
+ @Test
+ void connectSelfLoop() {
+ DiGraph g = new DiGraph();
+ Edge e = new Edge();
+ Node n = new Node();
+ g.addNode(n);
+ g.addEdge(e);
+ g.connect(n, e, n);
+ assertEquals(1, g.getNodes().size());
+ assertTrue(g.getNodes().contains(n));
+ assertEquals(1, g.getEdges().size());
+ assertSame(e, g.getEdges().get(0));
+ assertSame(e, n.getOutEdges().get(0));
+ assertSame(e, n.getInEdges().get(0));
+ assertSame(n, e.getFrom());
+ assertSame(n, e.getTo());
+ }
+
+ @Test
+ void connectSameEdgeTwice() {
+ DiGraph g = new DiGraph();
+ Edge e = new Edge();
+ Node n1 = new Node();
+ Node n2 = new Node();
+ g.addNode(n1);
+ g.addNode(n2);
+ g.addEdge(e);
+ g.connect(n1, e, n2);
+ assertThrows(IllegalStateException.class, () -> g.connect(n1, e, n2));
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/graph/EdgeTest.java b/test/ch/usi/inf/sp/graph/EdgeTest.java
new file mode 100644
index 0000000..650f4c0
--- /dev/null
+++ b/test/ch/usi/inf/sp/graph/EdgeTest.java
@@ -0,0 +1,46 @@
+package ch.usi.inf.sp.graph;
+
+import ch.usi.inf.sp.graph.Edge;
+import ch.usi.inf.sp.graph.Node;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class EdgeTest {
+
+ @Test
+ void newEdge() {
+ Edge e = new Edge();
+ assertNull(e.getFrom());
+ assertNull(e.getTo());
+ }
+
+ @Test
+ void toStringTest() {
+ Node n1 = new Node();
+ Node n2 = new Node();
+ Edge e = new Edge();
+ e.setFrom(n1);
+ e.setTo(n2);
+ assertEquals(n1.toString()+" -> "+n2.toString(), e.toString());
+ }
+
+ @Test
+ void setFrom() {
+ Edge e = new Edge();
+ Node n = new Node();
+ e.setFrom(n);
+ assertEquals(n, e.getFrom());
+ assertNull(e.getTo());
+ }
+
+ @Test
+ void setTo() {
+ Edge e = new Edge();
+ Node n = new Node();
+ e.setTo(n);
+ assertEquals(n, e.getTo());
+ assertNull(e.getFrom());
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/graph/NodeTest.java b/test/ch/usi/inf/sp/graph/NodeTest.java
new file mode 100644
index 0000000..005f8af
--- /dev/null
+++ b/test/ch/usi/inf/sp/graph/NodeTest.java
@@ -0,0 +1,42 @@
+package ch.usi.inf.sp.graph;
+
+import ch.usi.inf.sp.graph.Edge;
+import ch.usi.inf.sp.graph.Node;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class NodeTest {
+
+ @Test
+ void newNode() {
+ Node n = new Node();
+ assertEquals(0, n.getInEdges().size());
+ assertEquals(0, n.getOutEdges().size());
+ }
+
+ @Test
+ void toStringTest() {
+ Node n = new Node();
+ assertEquals('"'+n.getClass().getName()+"@"+Integer.toHexString(n.hashCode())+'"', n.toString());
+ }
+
+ @Test
+ void addInEdge() {
+ Node n = new Node();
+ Edge e = new Edge();
+ n.addInEdge(e);
+ assertEquals(1, n.getInEdges().size());
+ assertSame(e, n.getInEdges().get(0));
+ }
+
+ @Test
+ void addOutEdge() {
+ Node n = new Node();
+ Edge e = new Edge();
+ n.addOutEdge(e);
+ assertEquals(1, n.getOutEdges().size());
+ assertSame(e, n.getOutEdges().get(0));
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/usi/inf/sp/graph/TraversalTest.java b/test/ch/usi/inf/sp/graph/TraversalTest.java
new file mode 100644
index 0000000..884ee3e
--- /dev/null
+++ b/test/ch/usi/inf/sp/graph/TraversalTest.java
@@ -0,0 +1,229 @@
+package ch.usi.inf.sp.graph;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TraversalTest {
+
+ //--- simple DiGraph, Node, and Edge implementations just for testing
+ private static class TG extends DiGraph {
+ private TN root;
+ public TG(TN root) { this.root = root; }
+ public TN getRoot() { return root; }
+ public void edge(TN from, TN to) {
+ if (!getNodes().contains(from)) addNode(from);
+ if (!getNodes().contains(to)) addNode(to);
+ TE edge = new TE();
+ addEdge(edge);
+ connect(from, edge, to);
+ }
+ }
+ private static class TN extends Node {
+ private String label;
+ public TN(String label) { this.label = label; }
+ @Override
+ public String toString() {
+ return label;
+ }
+ }
+ private static class TE extends Edge {}
+
+
+ @Test
+ void getNodesInPostOrderTwoNodes() {
+ TN a = new TN("A");
+ TN b = new TN("B");
+ TG graph = new TG(a);
+ graph.edge(a, b);
+ //System.out.print(graph);
+
+ List postOrderNodes = Traversal.getNodesInPostOrder(graph, graph.getRoot());
+ //System.out.println("post order: "+postOrderNodes);
+ assertEquals(2, postOrderNodes.size());
+ assertIterableEquals(Arrays.asList(b, a), postOrderNodes);
+
+ List reversePostOrderNodes = Traversal.getNodesInReversePostOrder(graph, graph.getRoot());
+ //System.out.println("reverse post order: "+reversePostOrderNodes);
+ assertEquals(2, reversePostOrderNodes.size());
+ assertIterableEquals(Arrays.asList(a, b), reversePostOrderNodes);
+ }
+
+ @Test
+ void getNodesInPostOrderThreeNodes() {
+ TN a = new TN("A");
+ TN b = new TN("B");
+ TN c = new TN("C");
+ TG graph = new TG(a);
+ graph.edge(a, b);
+ graph.edge(b, c);
+ //System.out.print(graph);
+
+ List postOrderNodes = Traversal.getNodesInPostOrder(graph, graph.getRoot());
+ //System.out.println("post order: "+postOrderNodes);
+ assertEquals(3, postOrderNodes.size());
+ assertIterableEquals(Arrays.asList(new TN[] {c,b,a}), postOrderNodes);
+
+ List reversePostOrderNodes = Traversal.getNodesInReversePostOrder(graph, graph.getRoot());
+ //System.out.println("reverse post order: "+reversePostOrderNodes);
+ assertEquals(3, reversePostOrderNodes.size());
+ assertIterableEquals(Arrays.asList(new TN[] {a,b,c}), reversePostOrderNodes);
+ }
+
+ @Test
+ void getNodesInPostOrderDiamond() {
+ TN a = new TN("A");
+ TN b = new TN("B");
+ TN c = new TN("C");
+ TN d = new TN("D");
+ TG graph = new TG(a);
+ graph.edge(a, b);
+ graph.edge(a, c);
+ graph.edge(b, d);
+ graph.edge(c, d);
+ //System.out.print(graph);
+
+ List postOrderNodes = Traversal.getNodesInPostOrder(graph, graph.getRoot());
+ //System.out.println("post order: "+postOrderNodes);
+ assertEquals(4, postOrderNodes.size());
+ assertSame(d, postOrderNodes.get(0));
+ assertSame(a, postOrderNodes.get(3));
+
+ List reversePostOrderNodes = Traversal.getNodesInReversePostOrder(graph, graph.getRoot());
+ //System.out.println("reverse post order: "+reversePostOrderNodes);
+ assertEquals(4, reversePostOrderNodes.size());
+ assertSame(d, reversePostOrderNodes.get(3));
+ assertSame(a, reversePostOrderNodes.get(0));
+ }
+
+ @Test
+ void getNodesInPostOrderExample() {
+ // example graph from
+ // https://eli.thegreenplace.net/2015/directed-graph-traversal-orderings-and-applications-to-data-flow-analysis/
+ TN a = new TN("A");
+ TN b = new TN("B");
+ TN c = new TN("C");
+ TN d = new TN("D");
+ TN e = new TN("E");
+ TN t = new TN("T");
+ TG graph = new TG(a);
+ graph.edge(a, c);
+ graph.edge(a, b);
+ graph.edge(a, t);
+ graph.edge(c, b);
+ graph.edge(t, b);
+ graph.edge(c, e);
+ graph.edge(e, d);
+ graph.edge(b, d);
+ //System.out.print(graph);
+
+ List postOrderNodes = Traversal.getNodesInPostOrder(graph, graph.getRoot());
+ // this assertion is overly strict: it assumes the traversal algorithm iterates forward through the out edges
+ //System.out.println("post order: "+postOrderNodes);
+ assertEquals(6, postOrderNodes.size());
+ assertIterableEquals(Arrays.asList(new TN[] {d,b,e,c,t,a}), postOrderNodes);
+
+ List reversePostOrderNodes = Traversal.getNodesInReversePostOrder(graph, graph.getRoot());
+ // this assertion is overly strict: it assumes the traversal algorithm iterates forward through the out edges
+ //System.out.println("reverse post order: "+reversePostOrderNodes);
+ assertEquals(6, reversePostOrderNodes.size());
+ assertIterableEquals(Arrays.asList(new TN[] {a,t,c,e,b,d}), reversePostOrderNodes);
+ }
+
+ @Test
+ void getNodesInPostOrderExample2() {
+ // example graph from
+ // Christensen's MS thesis, Figure 2.2
+ TN a = new TN("A");
+ TN b = new TN("B");
+ TN c = new TN("C");
+ TN d = new TN("D");
+ TN e = new TN("E");
+ TN f = new TN("F");
+ TG graph = new TG(a);
+ graph.edge(a, b);
+ graph.edge(a, d);
+ graph.edge(b, c);
+ graph.edge(d, e);
+ graph.edge(d, f);
+ graph.edge(e, b);
+ graph.edge(f, e);
+ //System.out.print(graph);
+
+ List postOrderNodes = Traversal.getNodesInPostOrder(graph, graph.getRoot());
+ // this assertion is overly strict: it assumes the traversal algorithm iterates forward through the out edges
+ //System.out.println("post order: "+postOrderNodes);
+ assertIterableEquals(Arrays.asList(new TN[] {c,b,e,f,d,a}), postOrderNodes);
+
+ List reversePostOrderNodes = Traversal.getNodesInReversePostOrder(graph, graph.getRoot());
+ // this assertion is overly strict: it assumes the traversal algorithm iterates forward through the out edges
+ //System.out.println("reverse post order: "+reversePostOrderNodes);
+ assertIterableEquals(Arrays.asList(new TN[] {a,d,f,e,b,c}), reversePostOrderNodes);
+ }
+
+ @Test
+ void getNodesInPostOrderExample3() {
+ // example graph from
+ // Cooper et al., Figure 2
+ TN n1 = new TN("1");
+ TN n2 = new TN("2");
+ TN n3 = new TN("3");
+ TN n4 = new TN("4");
+ TN n5 = new TN("5");
+ TG graph = new TG(n5);
+ graph.edge(n5, n3);
+ graph.edge(n5, n4);
+ graph.edge(n3, n2);
+ graph.edge(n2, n1);
+ graph.edge(n1, n2);
+ graph.edge(n4, n1);
+ //System.out.print(graph);
+
+ List postOrderNodes = Traversal.getNodesInPostOrder(graph, graph.getRoot());
+ // this assertion is overly strict: it assumes the traversal algorithm iterates forward through the out edges
+ //System.out.println("post order: "+postOrderNodes);
+ assertIterableEquals(Arrays.asList(new TN[] {n1,n2,n3,n4,n5}), postOrderNodes);
+
+ List reversePostOrderNodes = Traversal.getNodesInReversePostOrder(graph, graph.getRoot());
+ // this assertion is overly strict: it assumes the traversal algorithm iterates forward through the out edges
+ //System.out.println("reverse post order: "+reversePostOrderNodes);
+ assertIterableEquals(Arrays.asList(new TN[] {n5,n4,n3,n2,n1}), reversePostOrderNodes);
+ }
+
+ @Test
+ void getNodesInPostOrderExample4() {
+ // example graph from
+ // Cooper et al., Figure 4
+ TN n1 = new TN("1");
+ TN n2 = new TN("2");
+ TN n3 = new TN("3");
+ TN n4 = new TN("4");
+ TN n5 = new TN("5");
+ TN n6 = new TN("6");
+ TG graph = new TG(n6);
+ graph.edge(n6, n4);
+ graph.edge(n6, n5);
+ graph.edge(n4, n3);
+ graph.edge(n4, n2);
+ graph.edge(n2, n1);
+ graph.edge(n2, n3);
+ graph.edge(n3, n2);
+ graph.edge(n5, n1);
+ graph.edge(n1, n2);
+ //System.out.print(graph);
+
+ List postOrderNodes = Traversal.getNodesInPostOrder(graph, graph.getRoot());
+ // this assertion is overly strict: it assumes the traversal algorithm iterates forward through the out edges
+ //System.out.println("post order: "+postOrderNodes);
+ assertIterableEquals(Arrays.asList(new TN[] {n1,n2,n3,n4,n5,n6}), postOrderNodes);
+
+ List reversePostOrderNodes = Traversal.getNodesInReversePostOrder(graph, graph.getRoot());
+ // this assertion is overly strict: it assumes the traversal algorithm iterates forward through the out edges
+ //System.out.println("reverse post order: "+reversePostOrderNodes);
+ assertIterableEquals(Arrays.asList(new TN[] {n6,n5,n4,n3,n2,n1}), reversePostOrderNodes);
+ }
+
+}
\ No newline at end of file