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); + *

+ * An example output: + * + *

+ * Class: ExampleClass
+ *     ...
+ *   Method: switchMethod2(I)I
+ *   0:	 	 // label
+ *   1:	 	 // line number information
+ *   2:		ICONST_0
+ *   3:		ISTORE 2
+ *   4:	 	 // label
+ *   5:		 // line number information
+ *   6:		ILOAD 1
+ *   7:		LOOKUPSWITCH 0: 8, 1000: 13, 2000: 18, default: 23
+ *   8:	 	 // label
+ *   9:	 	 // line number information
+ *   10:	ICONST_0
+ *   11:	ISTORE 2
+ *   12:	GOTO 27
+ *   13:	 // label
+ *   14:	 // line number information
+ *   15:	ICONST_1
+ *   16:	ISTORE 2
+ *   17:	GOTO 27
+ *   18:	 // label
+ *   19:	 // line number information
+ *   20:	ICONST_2
+ *   21:	ISTORE 2
+ *   22:	GOTO 27
+ *   23:	 // label
+ *   24:	 // line number information
+ *   25:	ICONST_M1
+ *   26:	ISTORE 2
+ *   27:	 // label
+ *   28:	 // line number information
+ *   29:	ILOAD 2
+ *   30:	IRETURN
+ *   31:	 // label
+ * 
+ * + * @author Matthias.Hauswirth@usi.ch + */ +public class Disassembler { + + public static void main(final String[] args) throws Exception { + // create a ClassReader that loads the Java .class file specified as the command line argument + final String classFileName = args[0]; + final ClassReader cr = new ClassReader(new FileInputStream(classFileName)); + // create an empty ClassNode (in-memory representation of a class) + final ClassNode clazz = new ClassNode(); + // have the ClassReader read the class file and populate the ClassNode with the corresponding information + cr.accept(clazz, 0); + // create a dumper and have it dump the given ClassNode + System.out.println(disassembleClass(clazz)); + } + + public static String disassembleClass(final ClassNode clazz) { + final StringBuffer sb = new StringBuffer("Class: "); + sb.append(clazz.name); + sb.append('\n'); + // get the list of all methods in that class + final List 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