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