Initial commit

This commit is contained in:
github-classroom[bot] 2023-10-18 19:57:49 +00:00 committed by GitHub
commit 74a433f602
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 2719 additions and 0 deletions

2
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@ -0,0 +1,19 @@
<component name="libraryTable">
<library name="asm-util-7.1">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/asm-util-7.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/asm-tree-7.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/asm-7.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$PROJECT_DIR$/lib/asm-util-7.1-javadoc.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/asm-tree-7.1-javadoc.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/asm-7.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$PROJECT_DIR$/lib/asm-util-7.1-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/asm-tree-7.1-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/asm-7.1-sources.jar!/" />
</SOURCES>
</library>
</component>

10
.idea/misc.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ASMPluginConfiguration">
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
<groovy codeStyle="LEGACY" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="10" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/starter-lab-04-dominator-analysis.iml" filepath="$PROJECT_DIR$/starter-lab-04-dominator-analysis.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="App" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="ch.usi.inf.sp.cfg.App" />
<module name="starter-lab-04-dominator-analysis" />
<option name="PROGRAM_PARAMETERS" value="test-input/java10/ExampleClass.class test-output" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="ch.usi.inf.sp.graph.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Disassembler" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="ch.usi.inf.sp.bytecode.Disassembler" />
<module name="starter-lab-03-control-flow-graph" />
<option name="PROGRAM_PARAMETERS" value="test-input/java10/ExampleClass.class" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="ch.usi.inf.sp.bytecode.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,3 @@
9
README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d

124
.idea/uiDesigner.xml Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

34
README.md Normal file
View File

@ -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

BIN
lib/asm-7.1-javadoc.jar Normal file

Binary file not shown.

BIN
lib/asm-7.1-sources.jar Normal file

Binary file not shown.

BIN
lib/asm-7.1.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/asm-tree-7.1.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/asm-util-7.1.jar Normal file

Binary file not shown.

View File

@ -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.
* <p>
* 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);
* <p>
* An example output:
*
* <pre>
* 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
* </pre>
*
* @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<MethodNode> 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
* <p>
* 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();
}
}

View File

@ -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...
* <p>
* java App test-input/java10/ExampleClass.class test-output
* <p>
* 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...
* <p>
* dot -Tpdf -oall.pdf *.dot
* <p>
* ...to produce a file all.pdf containing one page for each graph, or...
* <p>
* dot -Tpdf -oall.combined.pdf *.combined.pdf
* </p>
* ...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<MethodNode> 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));
}
}

View File

@ -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<ControlFlowEdge> {
private final int id;
private final ArrayList<String> instructions;
public BasicBlock(final int id) {
this.id = id;
instructions = new ArrayList<String>();
}
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<String> getInstructions() {
return instructions;
}
@Override
public String toString() {
return "\""+id+"\"";
}
}

View File

@ -0,0 +1,19 @@
package ch.usi.inf.sp.cfg;
import ch.usi.inf.sp.graph.Edge;
public final class ControlFlowEdge extends Edge<BasicBlock> {
private final String label;
public ControlFlowEdge(final String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}

View File

@ -0,0 +1,110 @@
package ch.usi.inf.sp.cfg;
import ch.usi.inf.sp.graph.DiGraph;
public final class ControlFlowGraph extends DiGraph<BasicBlock, ControlFlowEdge> {
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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,8 @@
package ch.usi.inf.sp.dom;
import ch.usi.inf.sp.graph.Edge;
public class DominanceEdge extends Edge<DominatorTreeNode> {
}

View File

@ -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(...)
}

View File

@ -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<DominatorTreeNode,DominanceEdge> {
private Map<BasicBlock,DominatorTreeNode> 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();
}
}

View File

@ -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<DominanceEdge> {
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()+")\"";
}
}

View File

@ -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();
}
}

View File

@ -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<N extends Node<E>, E extends Edge<N>> {
private final ArrayList<N> nodes;
private final ArrayList<E> edges;
public DiGraph() {
nodes = new ArrayList<N>();
edges = new ArrayList<E>();
}
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<E> getEdges() {
return Collections.unmodifiableList(edges);
}
public List<N> 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();
}
}

View File

@ -0,0 +1,32 @@
package ch.usi.inf.sp.graph;
public class Edge<N extends Node> {
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();
}
}

View File

@ -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<E extends Edge> {
private final ArrayList<E> inEdges;
private final ArrayList<E> outEdges;
public Node() {
inEdges = new ArrayList<E>();
outEdges = new ArrayList<E>();
}
public void addInEdge(E edge) {
inEdges.add(edge);
}
public void addOutEdge(E edge) {
outEdges.add(edge);
}
public List<E> getInEdges() {
return Collections.unmodifiableList(inEdges);
}
public List<E> getOutEdges() {
return Collections.unmodifiableList(outEdges);
}
public String toString() {
return '"'+super.toString()+'"';
}
}

View File

@ -0,0 +1,39 @@
package ch.usi.inf.sp.graph;
import java.util.*;
public class Traversal {
public static <G extends DiGraph<N, E>, N extends Node<E>, E extends Edge<N>>
List<N> getNodesInReversePostOrder(final DiGraph<N, E> graph, final N entryNode) {
final List<N> 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 <G extends DiGraph<N, E>, N extends Node<E>, E extends Edge<N>>
List<N> getNodesInPostOrder(final DiGraph<N, E> graph, final N entryNode) {
//TODO
return null;
}
// probably add a method dfsWalk(...)
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library" scope="TEST">
<library name="JUnit5.2">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.3.1/junit-jupiter-api-5.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="library" name="asm-util-7.1" level="project" />
</component>
</module>

View File

@ -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; j<i; i++) {
sum += j;
}
return sum;
}
public int whileMethod(int i) {
int sum = 0;
while (i>0) {
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<String> a) {
int sum = 0;
for (String s : a) {
sum++;
}
return sum;
}
public int forWithBreakMethod(int n) {
int sum = 0;
for (int i=0; i<n; i++) {
if (i==10) {
break;
}
sum += i;
}
return sum;
}
public int forWithContinueMethod(int n) {
int sum = 0;
for (int i=0; i<n; i++) {
if (i==10) {
continue;
}
sum += i;
}
return sum;
}
public int whileTrueMethod(int n) {
while (true) {
n++;
}
}
public int doWhileTrue(int n) {
do {
n++;
} while (true);
}
public int forEver(int n) {
for (int i=0; true; i++) {
}
}
public int nestedFor(int n) {
int sum = 0;
for (int i=0; i<n; i++) {
for (int j=0; j<i; j++) {
sum += j;
}
}
return sum;
}
//--- calls
public int staticCallMethod(int i) {
staticCallTarget();
return 2;
}
public int instanceCallMethod(ExampleClass i) {
i.instanceCallTarget();
return 2;
}
public int privateInstanceCallMethod(ExampleClass i) {
i.privateInstanceCallTarget();
return 2;
}
public int interfaceCallMethod(Interface i) {
i.interfaceCallTarget();
return 2;
}
static interface Interface {
public void interfaceCallTarget();
}
static class Implementation implements Interface {
public void interfaceCallTarget() {
return;
}
}
public static void staticCallTarget() {
}
public void instanceCallTarget() {
}
private void privateInstanceCallTarget() {
}
//--- field and array accesses
private String field;
public String fieldReadMethod() {
return field;
}
public void fieldWriteMethod(String s) {
field = s;
}
private static String staticField;
public String staticFieldReadMethod() {
return staticField;
}
public void staticFieldWriteMethod(String s) {
staticField = s;
}
public int arrayLengthMethod(String[] a) {
return a.length;
}
public String arrayReadMethod(String[] a) {
return a[0];
}
public void arrayWriteMethod(String[] a, String s) {
a[0] = s;
}
//--- allocation
public Object allocObjectMethod() {
return new Object();
}
public int[] allocIntArrayMethod() {
return new int[3];
}
public Object[] allocObjectArrayMethod() {
return new Object[3];
}
public int[][] alloc2dArrayMethod() {
return new int[2][3];
}
public int[][] allocIncomplete2dArrayMethod() {
return new int[2][];
}
public int[][][] alloc2Of3dArrayMethod() {
return new int[2][3][];
}
public int[] allocAndInitIntArrayMethod() {
return new int[] {1, 2};
}
public Object[] allocAndInitObjectArrayMethod() {
return new Object[] {"1", "2"};
}
public int[][] allocAndInit2dArrayMethod() {
return new int[][] {{1}};
}
//--- more conditionals
public int condMethod(int a, int b) {
return a>b?a:b;
}
public int shortCircuitMethod(int i, int j, int k) {
if (i>j && i<k) {
return 1;
}
return 0;
}
public int nonShortCircuitMethod(int i, int j, int k) {
if (i>j & i<k) {
return 1;
}
return 0;
}
}

9
test-input/Mini.java Normal file
View File

@ -0,0 +1,9 @@
// A minimal class, with one "method" (the constructor)
public class Mini {
public Mini() {
super();
return;
}
}

16
test-input/compile-example.sh Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Compile the given Java file for the different versions of Java
# E.g., if you use the ASM Viewer plugin for IntelliJ IDEA,
# that plugin can't deal with Java 10.
# It can read Java class files for older versions of Java, though.
# So, here we compile it for all versions of Java supported by the Java 10 javac compiler.
CLASS=$1
VERSIONS=( 6 7 8 9 10 )
for VERSION in "${VERSIONS[@]}"
do
echo Version $VERSION
mkdir -p java$VERSION
javac -d java$VERSION --release $VERSION $CLASS
done

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
test-input/java6/Mini.class Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
test-input/java7/Mini.class Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
test-input/java8/Mini.class Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
test-input/java9/Mini.class Normal file

Binary file not shown.

View File

@ -0,0 +1,28 @@
package ch.usi.inf.sp.cfg;
import org.junit.jupiter.api.Test;
import java.util.Iterator;
import static org.junit.jupiter.api.Assertions.*;
class BasicBlockTest {
@Test
void newBasicBlock() {
BasicBlock bb = new BasicBlock(1);
assertEquals(1, bb.getId());
assertFalse(bb.getInstructions().iterator().hasNext());
}
@Test
void appendInstruction() {
BasicBlock bb = new BasicBlock(1);
bb.appendInstruction("i1");
bb.appendInstruction("i2");
Iterator<String> it = bb.getInstructions().iterator();
assertEquals("i1", it.next());
assertEquals("i2", it.next());
}
}

View File

@ -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());
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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<BasicBlock> 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;
}
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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));
}
}

View File

@ -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<TN, TE> {
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<TE> {
private String label;
public TN(String label) { this.label = label; }
@Override
public String toString() {
return label;
}
}
private static class TE extends Edge<TN> {}
@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<TN> postOrderNodes = Traversal.getNodesInPostOrder(graph, graph.getRoot());
//System.out.println("post order: "+postOrderNodes);
assertEquals(2, postOrderNodes.size());
assertIterableEquals(Arrays.asList(b, a), postOrderNodes);
List<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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<TN> 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);
}
}