/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.coverage.paths;
import java.util.*;
import javax.annotation.*;
import mockit.coverage.paths.Node.*;
import mockit.external.asm.*;
public final class NodeBuilder
{
public int firstLine;
@Nonnull final List<Node> nodes = new ArrayList<Node>();
@Nullable private Entry entryNode;
@Nullable private SimpleFork currentSimpleFork;
@Nullable private BasicBlock currentBasicBlock;
@Nullable private Join currentJoin;
@Nonnull private final Map<Label, List<Fork>> jumpTargetToForks = new LinkedHashMap<Label, List<Fork>>();
@Nonnull private final Map<Label, List<GotoSuccessor>> gotoTargetToSuccessors =
new LinkedHashMap<Label, List<GotoSuccessor>>();
private int potentiallyTrivialJump;
public void handleEntry(int line)
{
firstLine = line;
entryNode = new Entry(line);
addNewNode(entryNode);
}
private int addNewNode(@Nonnull Node newNode)
{
int newNodeIndex = nodes.size();
if (newNodeIndex == 0 && !(newNode instanceof Entry)) {
return -1;
}
nodes.add(newNode);
if (newNodeIndex > 0) {
Node precedingNode = nodes.get(newNodeIndex - 1);
if (precedingNode.line == newNode.line) {
newNode.setSegmentAccordingToPrecedingNode(precedingNode);
}
}
return newNodeIndex;
}
public boolean hasNodes() { return !nodes.isEmpty(); }
public int handleRegularInstruction(int line, int opcode)
{
if (currentSimpleFork == null && currentJoin == null) {
potentiallyTrivialJump = 0;
return -1;
}
assert currentBasicBlock == null;
BasicBlock newNode = new BasicBlock(line);
connectNodes(newNode, opcode);
return addNewNode(newNode);
}
public int handleJump(@Nonnull Label targetBlock, int line, boolean conditional)
{
if (conditional) {
SimpleFork newFork = new SimpleFork(line);
assert currentSimpleFork == null;
connectNodes(targetBlock, newFork);
currentSimpleFork = newFork;
potentiallyTrivialJump = 1;
return addNewNode(newFork);
}
else if (currentBasicBlock == null && currentJoin == null) {
Goto newGoto = new Goto(line);
connectNodes(newGoto);
setUpMappingFromGotoTargetToCurrentGotoSuccessor(targetBlock, newGoto);
return addNewNode(newGoto);
}
else {
setUpMappingFromGotoTargetToCurrentGotoSuccessor(targetBlock, null);
return -1;
}
}
public int handleJumpTarget(@Nonnull Label basicBlock, int line)
{
// Ignore for visitLabel calls preceding visitLineNumber:
if (isNewLineTarget(basicBlock)) {
return -1;
}
Join newNode = new Join(line);
connectNodes(basicBlock, newNode);
return addNewNode(newNode);
}
private boolean isNewLineTarget(@Nonnull Label basicBlock)
{
return !jumpTargetToForks.containsKey(basicBlock) && !gotoTargetToSuccessors.containsKey(basicBlock);
}
private void connectNodes(@Nonnull BasicBlock newBasicBlock, int opcode)
{
if (currentSimpleFork != null) {
currentSimpleFork.nextConsecutiveNode = newBasicBlock;
currentSimpleFork = null;
if (potentiallyTrivialJump == 1) {
potentiallyTrivialJump = opcode == Opcodes.ICONST_1 ? 2 : 0;
}
}
else {
assert currentJoin != null;
if (potentiallyTrivialJump == 3) {
if (opcode == Opcodes.ICONST_0) {
currentJoin.fromTrivialFork = true;
}
potentiallyTrivialJump = 0;
}
currentJoin.nextNode = newBasicBlock;
currentJoin = null;
}
currentBasicBlock = newBasicBlock;
}
private void connectNodes(@Nonnull Label targetBlock, @Nonnull Fork newFork)
{
assert entryNode != null;
if (entryNode.nextNode == null) {
entryNode.nextNode = newFork;
}
setUpMappingFromConditionalTargetToFork(targetBlock, newFork);
connectNodes(newFork);
}
private void setUpMappingFromConditionalTargetToFork(@Nonnull Label targetBlock, @Nonnull Fork newFork)
{
List<Fork> forksWithSameTarget = jumpTargetToForks.get(targetBlock);
if (forksWithSameTarget == null) {
forksWithSameTarget = new LinkedList<Fork>();
jumpTargetToForks.put(targetBlock, forksWithSameTarget);
}
forksWithSameTarget.add(newFork);
}
private void setUpMappingFromGotoTargetToCurrentGotoSuccessor(@Nonnull Label targetBlock, @Nullable Goto gotoNode)
{
List<GotoSuccessor> successors = gotoTargetToSuccessors.get(targetBlock);
if (successors == null) {
successors = new LinkedList<GotoSuccessor>();
gotoTargetToSuccessors.put(targetBlock, successors);
}
// TODO: they both can be non-null here; what to do?
if (currentBasicBlock != null) {
assert currentJoin == null : "Ambiguous situation for " + targetBlock;
successors.add(currentBasicBlock);
currentBasicBlock = null;
if (potentiallyTrivialJump == 2) {
potentiallyTrivialJump = 3;
}
}
else if (currentJoin != null) {
successors.add(currentJoin);
currentJoin = null;
}
else {
successors.add(gotoNode);
}
}
private void connectNodes(@Nonnull Label basicBlock, @Nonnull Join newJoin)
{
connectNodes(newJoin);
connectSourceForksToTargetedJoin(basicBlock, newJoin);
connectGotoSuccessorsToNewJoin(basicBlock, newJoin);
currentJoin = newJoin;
}
public int handleExit(int exitLine)
{
Exit newNode = new Exit(exitLine);
connectNodes(newNode);
return addNewNode(newNode);
}
private void connectNodes(@Nonnull ConditionalSuccessor newNode)
{
if (currentSimpleFork != null) {
currentSimpleFork.nextConsecutiveNode = newNode;
currentSimpleFork = null;
assert currentJoin == null;
assert currentBasicBlock == null;
}
if (currentJoin != null) {
currentJoin.nextNode = newNode;
currentJoin = null;
assert currentBasicBlock == null;
}
if (currentBasicBlock != null) {
currentBasicBlock.nextConsecutiveNode = newNode;
currentBasicBlock = null;
}
}
private void connectSourceForksToTargetedJoin(@Nonnull Label targetBlock, @Nonnull Join newJoin)
{
List<Fork> forks = jumpTargetToForks.get(targetBlock);
if (forks != null) {
for (Fork fork : forks) {
fork.addNextNode(newJoin);
}
jumpTargetToForks.remove(targetBlock);
}
}
private void connectGotoSuccessorsToNewJoin(@Nonnull Label targetBlock, @Nonnull Join newJoin)
{
List<GotoSuccessor> successors = gotoTargetToSuccessors.get(targetBlock);
if (successors != null) {
for (GotoSuccessor successorToGoto : successors) {
successorToGoto.setNextNodeAfterGoto(newJoin);
}
gotoTargetToSuccessors.remove(targetBlock);
}
}
public int handleForwardJumpsToNewTargets(@Nonnull Label defaultBlock, @Nonnull Label[] caseBlocks, int line)
{
Fork newJoin = new MultiFork(line);
for (Label targetBlock : caseBlocks) {
if (targetBlock != defaultBlock) {
connectNodes(targetBlock, newJoin);
}
}
connectNodes(defaultBlock, newJoin);
return addNewNode(newJoin);
}
}