builder code is clean enough
This commit is contained in:
parent
8b36443331
commit
f4252692e6
7 changed files with 256 additions and 140 deletions
|
@ -3,6 +3,9 @@ package ch.usi.inf.sp.cfg;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import ch.usi.inf.sp.bytecode.Disassembler;
|
import ch.usi.inf.sp.bytecode.Disassembler;
|
||||||
|
import ch.usi.inf.sp.cfg.builder.CFGBuilder;
|
||||||
|
import ch.usi.inf.sp.cfg.builder.Edge;
|
||||||
|
import ch.usi.inf.sp.cfg.builder.MultiMap;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
import org.objectweb.asm.tree.InsnList;
|
import org.objectweb.asm.tree.InsnList;
|
||||||
|
@ -15,146 +18,7 @@ import org.objectweb.asm.tree.TableSwitchInsnNode;
|
||||||
|
|
||||||
public class ControlFlowGraphBuilder {
|
public class ControlFlowGraphBuilder {
|
||||||
|
|
||||||
private static class MultiMap<K, V> {
|
|
||||||
private final Map<K, List<V>> innerMap = new HashMap<>();
|
|
||||||
|
|
||||||
public void put(K key, V value) {
|
|
||||||
innerMap.computeIfAbsent(key, (k) -> new ArrayList<>());
|
|
||||||
innerMap.get(key).add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<V> getAll(K key) {
|
|
||||||
return new ArrayList<>(innerMap.getOrDefault(key, List.of()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsKey(K key) {
|
|
||||||
return innerMap.containsKey(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControlFlowGraph createControlFlowGraph(final MethodNode method) {
|
public static ControlFlowGraph createControlFlowGraph(final MethodNode method) {
|
||||||
final MultiMap<LabelNode, Edge> labelToIncomingEdges = new MultiMap<>();
|
return new CFGBuilder(method).create();
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
switch (instruction.getType()) {
|
|
||||||
case AbstractInsnNode.JUMP_INSN -> {
|
|
||||||
final JumpInsnNode jumpInsn = (JumpInsnNode) instruction;
|
|
||||||
labelToIncomingEdges.put(jumpInsn.label, new Edge(instruction, "T"));
|
|
||||||
}
|
}
|
||||||
case AbstractInsnNode.LOOKUPSWITCH_INSN -> {
|
|
||||||
final LookupSwitchInsnNode lookupSwitchInsnNode = (LookupSwitchInsnNode) instruction;
|
|
||||||
for (int j = 0; j < lookupSwitchInsnNode.labels.size(); j++) {
|
|
||||||
final LabelNode label = lookupSwitchInsnNode.labels.get(i);
|
|
||||||
final String value = lookupSwitchInsnNode.keys.get(i).toString();
|
|
||||||
labelToIncomingEdges.put(label, new Edge(instruction, value));
|
|
||||||
}
|
|
||||||
if (lookupSwitchInsnNode.dflt != null)
|
|
||||||
labelToIncomingEdges.put(lookupSwitchInsnNode.dflt, new Edge(instruction, "default"));
|
|
||||||
}
|
|
||||||
case AbstractInsnNode.TABLESWITCH_INSN -> {
|
|
||||||
final TableSwitchInsnNode tableSwitchInsnNode = (TableSwitchInsnNode) instruction;
|
|
||||||
for (int k = 0; k < tableSwitchInsnNode.labels.size(); k++) {
|
|
||||||
final LabelNode label = tableSwitchInsnNode.labels.get(i);
|
|
||||||
final String value = Integer.toString(tableSwitchInsnNode.min + k);
|
|
||||||
labelToIncomingEdges.put(label, new Edge(instruction, value));
|
|
||||||
}
|
|
||||||
if (tableSwitchInsnNode.dflt != null)
|
|
||||||
labelToIncomingEdges.put(tableSwitchInsnNode.dflt, new Edge(instruction, "default"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final ControlFlowGraph graph = new ControlFlowGraph();
|
|
||||||
final List<LabelNode> labels = new ArrayList<>();
|
|
||||||
final Map<AbstractInsnNode, BasicBlock> insnToBlock = new HashMap<>();
|
|
||||||
|
|
||||||
BasicBlock currentBasicBlock = new BasicBlock(0);
|
|
||||||
graph.addNode(currentBasicBlock);
|
|
||||||
|
|
||||||
graph.addEntryEdge(currentBasicBlock);
|
|
||||||
|
|
||||||
for (int i = 0; i < instructions.size(); i++) {
|
|
||||||
final AbstractInsnNode instruction = instructions.get(i);
|
|
||||||
final int opcode = instruction.getOpcode();
|
|
||||||
final int type = instruction.getType();
|
|
||||||
|
|
||||||
currentBasicBlock.appendInstruction(Disassembler.disassembleInstruction(instruction, i, instructions));
|
|
||||||
|
|
||||||
if (isReturnInstruction(opcode)) {
|
|
||||||
graph.addExitEdge(currentBasicBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean isPointedLabel = instruction instanceof LabelNode &&
|
|
||||||
labelToIncomingEdges.containsKey((LabelNode) instruction);
|
|
||||||
|
|
||||||
insnToBlock.put(instruction, currentBasicBlock);
|
|
||||||
|
|
||||||
if (isPointedLabel) {
|
|
||||||
labels.add((LabelNode) instruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEndOfBlock(instruction, labelToIncomingEdges)) {
|
|
||||||
final int nextI = i + 1;
|
|
||||||
|
|
||||||
// if we're not at the end
|
|
||||||
if (nextI < instructions.size()) {
|
|
||||||
final BasicBlock previousBasicBlock = currentBasicBlock;
|
|
||||||
|
|
||||||
currentBasicBlock = new BasicBlock(nextI);
|
|
||||||
graph.addNode(currentBasicBlock);
|
|
||||||
|
|
||||||
// Handle "implicit" edges from this basic block to the next one, or the exit node
|
|
||||||
if (instructions.get(nextI).getOpcode() != Opcodes.GOTO) {
|
|
||||||
graph.addFallthroughEdge(previousBasicBlock, currentBasicBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final LabelNode label : labels) {
|
|
||||||
final BasicBlock toBB = Objects.requireNonNull(insnToBlock.get(label));
|
|
||||||
for (final Edge incomingEdge : labelToIncomingEdges.getAll(label)) {
|
|
||||||
final BasicBlock fromBB = Objects.requireNonNull(insnToBlock.get(incomingEdge.instruction));
|
|
||||||
|
|
||||||
final ControlFlowEdge edge = new ControlFlowEdge(incomingEdge.condition);
|
|
||||||
graph.addEdge(edge);
|
|
||||||
graph.connect(fromBB, edge, toBB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return graph;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isReturnInstruction(int opcode) {
|
|
||||||
return opcode == Opcodes.RETURN
|
|
||||||
|| opcode == Opcodes.ARETURN
|
|
||||||
|| opcode == Opcodes.LRETURN
|
|
||||||
|| opcode == Opcodes.IRETURN
|
|
||||||
|| opcode == Opcodes.FRETURN;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isEndOfBlock(AbstractInsnNode instruction, MultiMap<LabelNode, Edge> labelToInstruction) {
|
|
||||||
final AbstractInsnNode nextInsn = instruction.getNext();
|
|
||||||
if (nextInsn == null) {
|
|
||||||
return false; // cannot start another bb at the end of the method with 0 instructions
|
|
||||||
}
|
|
||||||
|
|
||||||
final int type = instruction.getType();
|
|
||||||
if (type == AbstractInsnNode.JUMP_INSN
|
|
||||||
|| type == AbstractInsnNode.LOOKUPSWITCH_INSN
|
|
||||||
|| type == AbstractInsnNode.TABLESWITCH_INSN) {
|
|
||||||
return true; // if we're branching or jumping after this instruction, we NEED to cut the current bb short
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the next instruction is a label some other bb may jump into then cut, otherwise continue
|
|
||||||
return nextInsn instanceof LabelNode && labelToInstruction.containsKey((LabelNode) nextInsn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private record Edge(AbstractInsnNode instruction, String condition) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
134
src/ch/usi/inf/sp/cfg/builder/CFGBuilder.java
Normal file
134
src/ch/usi/inf/sp/cfg/builder/CFGBuilder.java
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package ch.usi.inf.sp.cfg.builder;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.bytecode.Disassembler;
|
||||||
|
import ch.usi.inf.sp.cfg.BasicBlock;
|
||||||
|
import ch.usi.inf.sp.cfg.ControlFlowEdge;
|
||||||
|
import ch.usi.inf.sp.cfg.ControlFlowGraph;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public final class CFGBuilder {
|
||||||
|
private final MultiMap<LabelNode, ch.usi.inf.sp.cfg.builder.Edge> labelToIncomingEdges = new MultiMap<>();
|
||||||
|
private final List<LabelNode> labels = new ArrayList<>();
|
||||||
|
private final Map<AbstractInsnNode, BasicBlock> insnToBlock = new HashMap<>();
|
||||||
|
|
||||||
|
private final ControlFlowGraph graph = new ControlFlowGraph();
|
||||||
|
private final MethodNode method;
|
||||||
|
private BasicBlock currentBasicBlock;
|
||||||
|
|
||||||
|
public CFGBuilder(MethodNode method) {
|
||||||
|
this.method = method;
|
||||||
|
setNewBasicBlock(0);
|
||||||
|
graph.addEntryEdge(currentBasicBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setNewBasicBlock(int id) {
|
||||||
|
currentBasicBlock = new BasicBlock(id);
|
||||||
|
graph.addNode(currentBasicBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControlFlowGraph create() {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
switch (instruction.getType()) {
|
||||||
|
case AbstractInsnNode.JUMP_INSN -> labelToIncomingEdges.put(
|
||||||
|
((JumpInsnNode) instruction).label,
|
||||||
|
new Edge(instruction, "T")
|
||||||
|
);
|
||||||
|
case AbstractInsnNode.LOOKUPSWITCH_INSN ->
|
||||||
|
captureSwitchEdges(new LookupSwitchInstructionNodeInfo((LookupSwitchInsnNode) instruction));
|
||||||
|
case AbstractInsnNode.TABLESWITCH_INSN ->
|
||||||
|
captureSwitchEdges(new TableSwitchInstructionNodeInfo((TableSwitchInsnNode) instruction));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < instructions.size(); i++) {
|
||||||
|
final AbstractInsnNode instruction = instructions.get(i);
|
||||||
|
|
||||||
|
currentBasicBlock.appendInstruction(Disassembler.disassembleInstruction(instruction, i, instructions));
|
||||||
|
|
||||||
|
if (isReturnInstruction(instruction)) {
|
||||||
|
graph.addExitEdge(currentBasicBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
insnToBlock.put(instruction, currentBasicBlock);
|
||||||
|
|
||||||
|
if (isInsnSignificantLabel(instruction)) {
|
||||||
|
labels.add((LabelNode) instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEndOfBlock(instruction)) {
|
||||||
|
final int nextI = i + 1;
|
||||||
|
|
||||||
|
final BasicBlock previousBasicBlock = currentBasicBlock;
|
||||||
|
setNewBasicBlock(nextI);
|
||||||
|
|
||||||
|
// Handle "implicit" edges from this basic block to the next one, or the exit node
|
||||||
|
if (instructions.get(nextI).getOpcode() != Opcodes.GOTO) {
|
||||||
|
graph.addFallthroughEdge(previousBasicBlock, currentBasicBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final LabelNode label : labels) {
|
||||||
|
final BasicBlock toBB = Objects.requireNonNull(insnToBlock.get(label));
|
||||||
|
for (final ch.usi.inf.sp.cfg.builder.Edge incomingEdge : labelToIncomingEdges.getAll(label)) {
|
||||||
|
final BasicBlock fromBB = Objects.requireNonNull(insnToBlock.get(incomingEdge.instruction()));
|
||||||
|
|
||||||
|
final ControlFlowEdge edge = new ControlFlowEdge(incomingEdge.condition());
|
||||||
|
graph.addEdge(edge);
|
||||||
|
graph.connect(fromBB, edge, toBB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void captureSwitchEdges(final SwitchInstructionNodeInfo info) {
|
||||||
|
for (int j = 0; j < info.getCaseCount(); j++) {
|
||||||
|
final LabelNode label = info.getLabelForCase(j);
|
||||||
|
final String value = info.getKeyForCase(j);
|
||||||
|
labelToIncomingEdges.put(label, new Edge(info.getNode(), value));
|
||||||
|
}
|
||||||
|
if (info.getDefaultCase() != null) {
|
||||||
|
labelToIncomingEdges.put(info.getDefaultCase(), new Edge(info.getNode(), "default"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInsnSignificantLabel(final AbstractInsnNode node) {
|
||||||
|
return node instanceof LabelNode && labelToIncomingEdges.containsKey((LabelNode) node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isReturnInstruction(final AbstractInsnNode instruction) {
|
||||||
|
final int opcode = instruction.getOpcode();
|
||||||
|
return opcode == Opcodes.RETURN ||
|
||||||
|
opcode == Opcodes.ARETURN ||
|
||||||
|
opcode == Opcodes.LRETURN ||
|
||||||
|
opcode == Opcodes.IRETURN ||
|
||||||
|
opcode == Opcodes.FRETURN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEndOfBlock(final AbstractInsnNode instruction) {
|
||||||
|
final AbstractInsnNode nextInsn = instruction.getNext();
|
||||||
|
|
||||||
|
if (nextInsn == null) {
|
||||||
|
return false; // cannot start another bb at the end of the method with 0 instructions
|
||||||
|
}
|
||||||
|
|
||||||
|
final int type = instruction.getType();
|
||||||
|
if (type == AbstractInsnNode.JUMP_INSN ||
|
||||||
|
type == AbstractInsnNode.LOOKUPSWITCH_INSN ||
|
||||||
|
type == AbstractInsnNode.TABLESWITCH_INSN) {
|
||||||
|
return true; // if we're branching or jumping after this instruction, we NEED to cut the current bb short
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the next instruction is a label some other bb may jump into then cut, otherwise continue
|
||||||
|
return isInsnSignificantLabel(nextInsn);
|
||||||
|
}
|
||||||
|
}
|
6
src/ch/usi/inf/sp/cfg/builder/Edge.java
Normal file
6
src/ch/usi/inf/sp/cfg/builder/Edge.java
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package ch.usi.inf.sp.cfg.builder;
|
||||||
|
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
|
||||||
|
public record Edge(AbstractInsnNode instruction, String condition) {
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ch.usi.inf.sp.cfg.builder;
|
||||||
|
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
|
import org.objectweb.asm.tree.LookupSwitchInsnNode;
|
||||||
|
|
||||||
|
public class LookupSwitchInstructionNodeInfo implements SwitchInstructionNodeInfo {
|
||||||
|
private final LookupSwitchInsnNode node;
|
||||||
|
|
||||||
|
public LookupSwitchInstructionNodeInfo(LookupSwitchInsnNode node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractInsnNode getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCaseCount() {
|
||||||
|
return node.labels.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LabelNode getLabelForCase(int index) {
|
||||||
|
return node.labels.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKeyForCase(int index) {
|
||||||
|
return Integer.toString(node.keys.get(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LabelNode getDefaultCase() {
|
||||||
|
return node.dflt;
|
||||||
|
}
|
||||||
|
}
|
23
src/ch/usi/inf/sp/cfg/builder/MultiMap.java
Normal file
23
src/ch/usi/inf/sp/cfg/builder/MultiMap.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package ch.usi.inf.sp.cfg.builder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MultiMap<K, V> {
|
||||||
|
private final Map<K, List<V>> innerMap = new HashMap<>();
|
||||||
|
|
||||||
|
public void put(K key, V value) {
|
||||||
|
innerMap.computeIfAbsent(key, (k) -> new ArrayList<>());
|
||||||
|
innerMap.get(key).add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<V> getAll(K key) {
|
||||||
|
return new ArrayList<>(innerMap.getOrDefault(key, List.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsKey(K key) {
|
||||||
|
return innerMap.containsKey(key);
|
||||||
|
}
|
||||||
|
}
|
13
src/ch/usi/inf/sp/cfg/builder/SwitchInstructionNodeInfo.java
Normal file
13
src/ch/usi/inf/sp/cfg/builder/SwitchInstructionNodeInfo.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package ch.usi.inf.sp.cfg.builder;
|
||||||
|
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
|
|
||||||
|
public interface SwitchInstructionNodeInfo {
|
||||||
|
AbstractInsnNode getNode();
|
||||||
|
int getCaseCount();
|
||||||
|
LabelNode getLabelForCase(int index);
|
||||||
|
String getKeyForCase(int index);
|
||||||
|
|
||||||
|
LabelNode getDefaultCase();
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ch.usi.inf.sp.cfg.builder;
|
||||||
|
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
|
import org.objectweb.asm.tree.TableSwitchInsnNode;
|
||||||
|
|
||||||
|
public class TableSwitchInstructionNodeInfo implements SwitchInstructionNodeInfo {
|
||||||
|
private final TableSwitchInsnNode node;
|
||||||
|
|
||||||
|
public TableSwitchInstructionNodeInfo(TableSwitchInsnNode node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractInsnNode getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCaseCount() {
|
||||||
|
return node.labels.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LabelNode getLabelForCase(int index) {
|
||||||
|
return node.labels.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKeyForCase(int index) {
|
||||||
|
return Integer.toString(node.min + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LabelNode getDefaultCase() {
|
||||||
|
return node.dflt;
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue