/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.dfa.pathfinder;
import javax.swing.tree.DefaultMutableTreeNode;
import net.sourceforge.pmd.lang.dfa.DataFlowNode;
import net.sourceforge.pmd.lang.dfa.NodeType;
/**
* Finds all paths of a data flow. Each loop will be 0 or 2 times traversed -> 2
* paths. This is special to the data flow anomaly analysis.
*
* @author raik
* @since Created on 09.08.2004
*/
public class DAAPathFinder {
private static final int MAX_PATHS = 5000;
/**
* Maximum loops to prevent hanging of PMD. See
* https://sourceforge.net/p/pmd/bugs/1393/
*/
private static final int MAX_LOOPS = 100;
private DataFlowNode rootNode;
private Executable shim;
private CurrentPath currentPath = new CurrentPath();
private DefaultMutableTreeNode stack = new DefaultMutableTreeNode();
private int maxPaths;
public DAAPathFinder(DataFlowNode rootNode, Executable shim) {
this.rootNode = rootNode;
this.shim = shim;
this.maxPaths = MAX_PATHS;
}
public DAAPathFinder(DataFlowNode rootNode, Executable shim, int maxPaths) {
this.rootNode = rootNode;
this.shim = shim;
this.maxPaths = maxPaths;
}
public void run() {
phase1();
}
/*
* Initialise the path search. Starts the searching.
*/
private void phase1() {
currentPath.addLast(rootNode);
int i = 0;
boolean flag = true;
do {
i++;
// System.out.println("Building path from " +
// currentPath.getLast());
phase2(flag);
shim.execute(currentPath);
flag = false;
} while (i < maxPaths && phase3());
}
/*
* Builds up the path.
*/
private void phase2(boolean flag) {
int i = 0;
while (!currentPath.isEndNode() && i < MAX_LOOPS) {
i++;
if (currentPath.isBranch() || currentPath.isFirstDoStatement()) {
if (flag) {
addNodeToTree();
}
flag = true;
if (countLoops() <= 2) {
addCurrentChild();
continue;
} else {
// jump out of that loop
addLastChild();
continue;
}
} else {
addCurrentChild();
}
}
}
/*
* Decompose the path until it finds a node which branches are not all
* traversed.
*/
private boolean phase3() {
while (!currentPath.isEmpty()) {
if (currentPath.isBranch()) {
if (this.countLoops() == 1) {
if (this.hasMoreChildren()) {
this.incChild();
return true;
} else {
this.removeFromTree();
currentPath.removeLast();
}
} else {
this.removeFromTree();
currentPath.removeLast();
}
} else {
currentPath.removeLast();
}
}
return false;
}
private boolean hasMoreChildren() {
PathElement e = (PathElement) stack.getLastLeaf().getUserObject();
return e.currentChild + 1 < e.node.getChildren().size();
}
private void addLastChild() {
PathElement e = (PathElement) stack.getLastLeaf().getUserObject();
for (int i = e.node.getChildren().size() - 1; i >= 0; i--) {
if (i != e.currentChild) {
currentPath.addLast(e.node.getChildren().get(i));
break;
}
}
}
private void addCurrentChild() {
if (currentPath.isBranch()) { // TODO WHY????
PathElement last = (PathElement) stack.getLastLeaf().getUserObject();
DataFlowNode inode = currentPath.getLast();
if (inode.getChildren().size() > last.currentChild) {
// for some unknown reasons last.currentChild might not be a
// children of inode, see bug 1597987
// see https://sourceforge.net/p/pmd/bugs/606/
DataFlowNode child = inode.getChildren().get(last.currentChild);
this.currentPath.addLast(child);
}
} else {
DataFlowNode inode = currentPath.getLast();
// TODO ???? IMPORTANT - ERROR?
DataFlowNode child = inode.getChildren().get(0);
this.currentPath.addLast(child);
}
}
// ----------------------------------------------------------------------------
// TREE FUNCTIONS
/*
* Adds a PathElement to a Tree, which contains information about loops and
* "local scopes - encapsulation".
*/
private void addNodeToTree() {
if (currentPath.isFirstDoStatement()) {
DefaultMutableTreeNode level = stack;
DataFlowNode doBranch = currentPath.getDoBranchNodeFromFirstDoStatement();
while (true) {
if (level.getChildCount() != 0) {
PathElement ref = this.isNodeInLevel(level);
if (ref != null) {
this.addRefPseudoPathElement(level, ref);
break;
} else {
level = this.getLastChildNode(level);
continue;
}
} else {
this.addNewPseudoPathElement(level, doBranch);
break;
}
}
}
if (currentPath.isBranch()) {
DefaultMutableTreeNode level = stack;
if (currentPath.isDoBranchNode()) {
while (!this.equalsPseudoPathElementWithDoBranchNodeInLevel(level)) {
level = this.getLastChildNode(level);
if (level.getChildCount() == 0) {
break;
}
}
PathElement ref = this.getDoBranchNodeInLevel(level);
if (ref != null) {
addNode(level, ref);
} else {
this.addNewPathElement(level);
}
} else {
while (true) {
if (level.getChildCount() != 0) {
PathElement ref = this.isNodeInLevel(level);
if (ref != null) {
addNode(level, ref);
break;
} else {
level = this.getLastChildNode(level);
continue;
}
} else {
this.addNewPathElement(level);
break;
}
}
}
}
}
private void removeFromTree() {
DefaultMutableTreeNode last = stack.getLastLeaf();
if (last == null) {
System.out.println("removeFromTree - last == null");
return;
}
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) last.getParent();
if (parent != null) {
// for some unknown reasons parent might be null, see bug 1597987
parent.remove(last);
}
last = stack.getLastLeaf();
if (last == null || last.getUserObject() == null) {
return;
}
PathElement e = (PathElement) last.getUserObject();
if (e != null && e.isPseudoPathElement()) {
this.removeFromTree();
}
}
private void addNewPathElement(DefaultMutableTreeNode level) {
addNode(level, new PathElement(currentPath.getLast()));
}
/*
* Needed for do loops
*/
private void addNewPseudoPathElement(DefaultMutableTreeNode level, DataFlowNode ref) {
addNode(level, new PathElement(currentPath.getLast(), ref));
}
/*
* Needed for do loops
*/
private void addRefPseudoPathElement(DefaultMutableTreeNode level, PathElement ref) {
addNode(level, ref);
}
private boolean equalsPseudoPathElementWithDoBranchNodeInLevel(DefaultMutableTreeNode level) {
DataFlowNode inode = currentPath.getLast();
if (!inode.isType(NodeType.DO_EXPR)) {
return false;
}
int childCount = level.getChildCount();
DefaultMutableTreeNode child;
for (int i = 0; i < childCount; i++) {
child = (DefaultMutableTreeNode) level.getChildAt(i);
PathElement pe = (PathElement) child.getUserObject();
if (pe != null && pe.isPseudoPathElement() && pe.pseudoRef.equals(inode)) {
return true;
}
}
return false;
}
private PathElement getDoBranchNodeInLevel(DefaultMutableTreeNode level) {
DataFlowNode inode = currentPath.getLast();
if (!inode.isType(NodeType.DO_EXPR)) {
return null;
}
int childCount = level.getChildCount();
DefaultMutableTreeNode child;
for (int i = 0; i < childCount; i++) {
child = (DefaultMutableTreeNode) level.getChildAt(i);
PathElement pe = (PathElement) child.getUserObject();
if (inode.equals(pe.node)) {
return pe;
}
}
return null;
}
private void addNode(DefaultMutableTreeNode level, PathElement element) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
node.setUserObject(element);
level.add(node);
}
private PathElement isNodeInLevel(DefaultMutableTreeNode level) {
DataFlowNode inode = currentPath.getLast();
DefaultMutableTreeNode child = (DefaultMutableTreeNode) level.getFirstChild();
if (child != null) {
PathElement levelElement = (PathElement) child.getUserObject();
if (inode.equals(levelElement.node)) {
return levelElement;
}
}
return null;
}
private DefaultMutableTreeNode getLastChildNode(DefaultMutableTreeNode node) {
if (node.getChildCount() != 0) {
return (DefaultMutableTreeNode) node.getLastChild();
}
return node;
}
private int countLoops() {
DefaultMutableTreeNode treeNode = stack.getLastLeaf();
int counter = 0;
if (treeNode.getParent() != null) {
// for some unknown reasons the parent of treeNode might be null,
// see bug 1597987
// see https://sourceforge.net/p/pmd/bugs/606/
int childCount = treeNode.getParent().getChildCount();
for (int i = 0; i < childCount; i++) {
DefaultMutableTreeNode tNode = (DefaultMutableTreeNode) treeNode.getParent().getChildAt(i);
PathElement e = (PathElement) tNode.getUserObject();
if (e != null && !e.isPseudoPathElement()) {
counter++;
}
}
}
return counter;
}
private void incChild() {
((PathElement) stack.getLastLeaf().getUserObject()).currentChild++;
}
}