package org.pitest.coverage.analysis; import static org.objectweb.asm.Opcodes.ARETURN; import static org.objectweb.asm.Opcodes.ATHROW; import static org.objectweb.asm.Opcodes.DRETURN; import static org.objectweb.asm.Opcodes.FRETURN; import static org.objectweb.asm.Opcodes.IRETURN; import static org.objectweb.asm.Opcodes.LRETURN; import static org.objectweb.asm.Opcodes.RETURN; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Set; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FrameNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.LookupSwitchInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TableSwitchInsnNode; import org.objectweb.asm.tree.TryCatchBlockNode; public class ControlFlowAnalyser { private static final int LIKELY_NUMBER_OF_LINES_PER_BLOCK = 7; public static List<Block> analyze(final MethodNode mn) { final List<Block> blocks = new ArrayList<Block>(mn.instructions.size()); final Set<AbstractInsnNode> jumpTargets = findJumpTargets(mn.instructions); // not managed to construct bytecode to show need for this // as try catch blocks usually have jumps at their boundaries anyway. // so possibly useless, but here for now. Because fear. addtryCatchBoundaries(mn, jumpTargets); Set<Integer> blockLines = smallSet(); int lastLine = Integer.MIN_VALUE; final int lastInstruction = mn.instructions.size() - 1; int blockStart = 0; for (int i = 0; i != mn.instructions.size(); i++) { final AbstractInsnNode ins = mn.instructions.get(i); if (ins instanceof LineNumberNode) { final LineNumberNode lnn = (LineNumberNode) ins; blockLines.add(lnn.line); lastLine = lnn.line; } else if (jumpTargets.contains(ins) && (blockStart != i)) { blocks.add(new Block(blockStart, i - 1, blockLines)); blockStart = i; blockLines = smallSet(); } else if (endsBlock(ins)) { blocks.add(new Block(blockStart, i, blockLines)); blockStart = i + 1; blockLines = smallSet(); } else if ((lastLine != Integer.MIN_VALUE) && isInstruction(ins)) { blockLines.add(lastLine); } } // this will not create a block if the last block contains only a single // instruction. // In the case of the hanging labels that eclipse compiler seems to generate // this is desirable. // Not clear if this will create problems in other scenarios if (blockStart != lastInstruction) { blocks.add(new Block(blockStart, lastInstruction, blockLines)); } return blocks; } private static HashSet<Integer> smallSet() { return new HashSet<Integer>(LIKELY_NUMBER_OF_LINES_PER_BLOCK); } private static boolean isInstruction(final AbstractInsnNode ins) { return !((ins instanceof LabelNode) || (ins instanceof FrameNode)); } private static void addtryCatchBoundaries(final MethodNode mn, final Set<AbstractInsnNode> jumpTargets) { for (final Object each : mn.tryCatchBlocks) { final TryCatchBlockNode tcb = (TryCatchBlockNode) each; jumpTargets.add(tcb.handler); } } private static boolean endsBlock(final AbstractInsnNode ins) { return (ins instanceof JumpInsnNode) || isReturn(ins); } private static boolean isReturn(final AbstractInsnNode ins) { final int opcode = ins.getOpcode(); switch (opcode) { case RETURN: case ARETURN: case DRETURN: case FRETURN: case IRETURN: case LRETURN: case ATHROW: return true; } return false; } @SuppressWarnings("unchecked") // asm jar has no generics info private static Set<AbstractInsnNode> findJumpTargets(final InsnList instructions) { final Set<AbstractInsnNode> jumpTargets = new HashSet<AbstractInsnNode>(); final ListIterator<?> it = instructions.iterator(); while (it.hasNext()) { final Object o = it.next(); if (o instanceof JumpInsnNode) { jumpTargets.add(((JumpInsnNode) o).label); } else if (o instanceof TableSwitchInsnNode) { final TableSwitchInsnNode twn = (TableSwitchInsnNode) o; jumpTargets.add(twn.dflt); jumpTargets.addAll(twn.labels); } else if (o instanceof LookupSwitchInsnNode) { final LookupSwitchInsnNode lsn = (LookupSwitchInsnNode) o; jumpTargets.add(lsn.dflt); jumpTargets.addAll(lsn.labels); } } return jumpTargets; } }