/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.dfa; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import net.sourceforge.pmd.lang.DataFlowHandler; import net.sourceforge.pmd.lang.ast.Node; /** * @author raik Links data flow nodes to each other. */ public class Linker { private static final Logger LOGGER = Logger.getLogger(Linker.class.getName()); private static final String CLASS_NAME = Linker.class.getCanonicalName(); /** * Maximum loops to prevent hanging of PMD. See * https://sourceforge.net/p/pmd/bugs/1393/ */ private static final int MAX_LOOPS = 100; private final DataFlowHandler dataFlowHandler; private List<StackObject> braceStack; private List<StackObject> continueBreakReturnStack; public Linker(DataFlowHandler dataFlowHandler, List<StackObject> braceStack, List<StackObject> continueBreakReturnStack) { this.dataFlowHandler = dataFlowHandler; this.braceStack = braceStack; this.continueBreakReturnStack = continueBreakReturnStack; } /** * Creates all the links between the data flow nodes. */ public void computePaths() throws LinkerException, SequenceException { LOGGER.entering(CLASS_NAME, "computePaths"); // Returns true if there are more sequences, computes the first and // the last index of the sequence. LOGGER.fine("SequenceChecking continueBreakReturnStack elements"); SequenceChecker sc = new SequenceChecker(braceStack); int i = 0; while (!sc.run() && i < MAX_LOOPS) { i++; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("After sc.run - starting Sequence checking loop with firstIndex=" + sc.getFirstIndex() + ", lastIndex " + sc.getLastIndex() + " with this StackList " + dump("braceStack", braceStack)); } if (sc.getFirstIndex() < 0 || sc.getLastIndex() < 0) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.severe("Sequence Checker problem: getFirstIndex()==" + sc.getFirstIndex() + ", getLastIndex()==" + sc.getLastIndex()); } throw new SequenceException("computePaths(): return index < 0"); } StackObject firstStackObject = braceStack.get(sc.getFirstIndex()); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Checking first braceStack element of type==" + NodeType.stringFromType(firstStackObject.getType())); } switch (firstStackObject.getType()) { case NodeType.IF_EXPR: LOGGER.finest("IF_EXPR"); int x = sc.getLastIndex() - sc.getFirstIndex(); if (x == 2) { this.computeIf(sc.getFirstIndex(), sc.getFirstIndex() + 1, sc.getLastIndex()); } else if (x == 1) { this.computeIf(sc.getFirstIndex(), sc.getLastIndex()); } else { LOGGER.severe("Error - computePaths 1"); } break; case NodeType.WHILE_EXPR: LOGGER.finest("WHILE_EXPR"); this.computeWhile(sc.getFirstIndex(), sc.getLastIndex()); break; case NodeType.SWITCH_START: LOGGER.finest("SWITCH_START"); this.computeSwitch(sc.getFirstIndex(), sc.getLastIndex()); break; case NodeType.FOR_INIT: case NodeType.FOR_EXPR: case NodeType.FOR_UPDATE: case NodeType.FOR_BEFORE_FIRST_STATEMENT: LOGGER.finest("FOR_EXPR"); this.computeFor(sc.getFirstIndex(), sc.getLastIndex()); break; case NodeType.DO_BEFORE_FIRST_STATEMENT: LOGGER.finest("DO_BEFORE_FIRST_STATEMENT"); this.computeDo(sc.getFirstIndex(), sc.getLastIndex()); break; default: } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Removing braces from Last to first: " + sc.getLastIndex() + " to " + sc.getFirstIndex()); } for (int y = sc.getLastIndex(); y >= sc.getFirstIndex(); y--) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Removing brace : " + y); } braceStack.remove(y); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Completed Sequence checking loop" + braceStack); } } if (LOGGER.isLoggable(Level.FINER)) { LOGGER.log(Level.FINER, "After Sequence checking loop : remaining braces=={0}", dump("braceStack", braceStack)); } LOGGER.fine("Processing continueBreakReturnStack elements"); while (!continueBreakReturnStack.isEmpty()) { LOGGER.fine("Starting continueBreakReturnStack processing loop"); StackObject stackObject = continueBreakReturnStack.get(0); DataFlowNode node = stackObject.getDataFlowNode(); switch (stackObject.getType()) { case NodeType.THROW_STATEMENT: // do the same like a return case NodeType.RETURN_STATEMENT: if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("CBR: " + NodeType.stringFromType(stackObject.getType())); } // remove all children (should contain only one child) node.removePathToChild(node.getChildren().get(0)); DataFlowNode lastNode = node.getFlow().get(node.getFlow().size() - 1); node.addPathToChild(lastNode); continueBreakReturnStack.remove(0); break; case NodeType.BREAK_STATEMENT: LOGGER.finest("CBR: BREAK_STATEMENT"); DataFlowNode last = getNodeToBreakStatement(node); node.removePathToChild(node.getChildren().get(0)); node.addPathToChild(last); continueBreakReturnStack.remove(0); break; case NodeType.CONTINUE_STATEMENT: LOGGER.finest("CBR: CONTINUE_STATEMENT"); // List cList = node.getFlow(); /* * traverse up the tree and find the first loop start node */ /* * for(int i = cList.indexOf(node)-1;i>=0;i--) { IDataFlowNode n * = (IDataFlowNode)cList.get(i); * * if(n.isType(NodeType.FOR_UPDATE) || * n.isType(NodeType.FOR_EXPR) || n.isType(NodeType.WHILE_EXPR)) * { */ /* * while(..) { while(...) { ... } continue; } * * Without this Expression he continues the second WHILE loop. * The continue statement and the while loop have to be in * different scopes. * * TODO An error occurs if "continue" is even nested in scopes * other than local loop scopes, like "if". The result is, that * the data flow isn't build right and the pathfinder runs in * invinity loop. */ /* * if(n.getNode().getScope().equals(node.getNode().getScope())) * { System.err.println("equals"); continue; } else { * System.err.println("don't equals"); } * * //remove all children (should contain only one child) * node.removePathToChild * ((IDataFlowNode)node.getChildren().get(0)); * * node.addPathToChild(n); cbrStack.remove(0); break; * * }else if(n.isType(NodeType.DO_BEFOR_FIRST_STATEMENT)) { * * IDataFlowNode inode = * (IDataFlowNode)n.getFlow().get(n.getIndex()1); * * for(int j=0;j<inode.getParents().size();j) { IDataFlowNode * parent = (IDataFlowNode)inode.getParents().get(j); * * if(parent.isType(NodeType.DO_EXPR)) { * node.removePathToChild(( * IDataFlowNode)node.getChildren().get(0)); * node.addPathToChild(parent); * * cbrStack.remove(0); break; } } break; } } */ // delete this statement if you uncomment the stuff above continueBreakReturnStack.remove(0); break; default: LOGGER.finest("CBR: default"); // Do nothing break; } LOGGER.fine("Completed continueBreakReturnStack processing loop"); } LOGGER.exiting(CLASS_NAME, "computePaths"); } private DataFlowNode getNodeToBreakStatement(DataFlowNode node) { LOGGER.entering(CLASS_NAME, "getNodeToBreakStatement"); // What about breaks to labels above if statements? List<DataFlowNode> bList = node.getFlow(); int findEnds = 1; // ignore ends of other for's while's etc. // find out index of the node where the path goes to after the break int index = bList.indexOf(node); for (; index < bList.size() - 2; index++) { DataFlowNode n = bList.get(index); if (n.isType(NodeType.DO_EXPR) || n.isType(NodeType.FOR_INIT) || n.isType(NodeType.WHILE_EXPR) || n.isType(NodeType.SWITCH_START)) { findEnds++; } if (n.isType(NodeType.WHILE_LAST_STATEMENT) || n.isType(NodeType.SWITCH_END) || n.isType(NodeType.FOR_END) || n.isType(NodeType.DO_EXPR)) { if (findEnds > 1) { // thats not the right node findEnds--; } else { break; } } if (n.isType(NodeType.LABEL_LAST_STATEMENT)) { Node parentNode = n.getNode().getFirstParentOfType(dataFlowHandler.getLabelStatementNodeClass()); if (parentNode == null) { break; } else { String label = node.getNode().getImage(); if (label == null || label.equals(parentNode.getImage())) { node.removePathToChild(node.getChildren().get(0)); DataFlowNode last = bList.get(index + 1); node.addPathToChild(last); break; } } } } LOGGER.exiting(CLASS_NAME, "getNodeToBreakSttement"); return node.getFlow().get(index + 1); } private void computeDo(int first, int last) { LOGGER.entering(CLASS_NAME, "computeDo"); DataFlowNode doSt = this.braceStack.get(first).getDataFlowNode(); DataFlowNode doExpr = this.braceStack.get(last).getDataFlowNode(); DataFlowNode doFirst = doSt.getFlow().get(doSt.getIndex() + 1); if (doFirst.getIndex() != doExpr.getIndex()) { doExpr.addPathToChild(doFirst); } LOGGER.exiting(CLASS_NAME, "computeDo"); } private void computeFor(int firstIndex, int lastIndex) { LOGGER.entering(CLASS_NAME, "computeFor"); DataFlowNode fExpr = null; DataFlowNode fUpdate = null; DataFlowNode fSt = null; DataFlowNode fEnd = null; boolean isUpdate = false; for (int i = firstIndex; i <= lastIndex; i++) { StackObject so = this.braceStack.get(i); DataFlowNode node = so.getDataFlowNode(); if (so.getType() == NodeType.FOR_EXPR) { fExpr = node; } else if (so.getType() == NodeType.FOR_UPDATE) { fUpdate = node; isUpdate = true; } else if (so.getType() == NodeType.FOR_BEFORE_FIRST_STATEMENT) { fSt = node; } else if (so.getType() == NodeType.FOR_END) { fEnd = node; } } DataFlowNode end = fEnd.getFlow().get(fEnd.getIndex() + 1); DataFlowNode firstSt = fSt.getChildren().get(0); if (isUpdate) { if (fSt.getIndex() != fEnd.getIndex()) { end.reverseParentPathsTo(fUpdate); fExpr.removePathToChild(fUpdate); fUpdate.addPathToChild(fExpr); fUpdate.removePathToChild(firstSt); fExpr.addPathToChild(firstSt); fExpr.addPathToChild(end); } else { fSt.removePathToChild(end); fExpr.removePathToChild(fUpdate); fUpdate.addPathToChild(fExpr); fExpr.addPathToChild(fUpdate); fExpr.addPathToChild(end); } } else { if (fSt.getIndex() != fEnd.getIndex()) { end.reverseParentPathsTo(fExpr); fExpr.addPathToChild(end); } } LOGGER.exiting(CLASS_NAME, "computeFor"); } private void computeSwitch(int firstIndex, int lastIndex) { LOGGER.entering(CLASS_NAME, "computeSwitch"); int diff = lastIndex - firstIndex; boolean defaultStatement = false; DataFlowNode sStart = this.braceStack.get(firstIndex).getDataFlowNode(); DataFlowNode sEnd = this.braceStack.get(lastIndex).getDataFlowNode(); DataFlowNode end = sEnd.getChildren().get(0); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Stack(sStart)=>" + sStart + ",Stack(sEnd)=>" + sEnd + ",end=>" + end); } for (int i = 0; i < diff - 2; i++) { StackObject so = this.braceStack.get(firstIndex + 2 + i); DataFlowNode node = so.getDataFlowNode(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("so(" + (firstIndex + 2 + i) + ")=>" + so + " has dfn=>" + node + " with first child =>" + node.getChildren().get(0)); } sStart.addPathToChild(node.getChildren().get(0)); if (so.getType() == NodeType.SWITCH_LAST_DEFAULT_STATEMENT) { defaultStatement = true; } } if (!defaultStatement) { sStart.addPathToChild(end); } LOGGER.exiting(CLASS_NAME, "computeSwitch"); } private void computeWhile(int first, int last) { LOGGER.entering(CLASS_NAME, "computeWhile with first and last of" + first + "," + last); DataFlowNode wStart = this.braceStack.get(first).getDataFlowNode(); DataFlowNode wEnd = this.braceStack.get(last).getDataFlowNode(); DataFlowNode end = wEnd.getFlow().get(wEnd.getIndex() + 1); if (wStart.getIndex() != wEnd.getIndex()) { end.reverseParentPathsTo(wStart); wStart.addPathToChild(end); } LOGGER.exiting(CLASS_NAME, "computeWhile"); } private void computeIf(int first, int second, int last) { LOGGER.entering(CLASS_NAME, "computeIf(3)", first + "," + second + "," + last); DataFlowNode ifStart = this.braceStack.get(first).getDataFlowNode(); DataFlowNode ifEnd = this.braceStack.get(second).getDataFlowNode(); DataFlowNode elseEnd = this.braceStack.get(last).getDataFlowNode(); DataFlowNode elseStart = ifEnd.getFlow().get(ifEnd.getIndex() + 1); DataFlowNode end = elseEnd.getFlow().get(elseEnd.getIndex() + 1); LOGGER.log(Level.FINEST, "If ifstart={0}, ifEnd={1}, elseEnd={2}, elseStart={3}, end={4}", new Object[] { ifStart, ifEnd, elseEnd, elseStart, end }); if (ifStart.getIndex() != ifEnd.getIndex() && ifEnd.getIndex() != elseEnd.getIndex()) { // if if-statement and else-statement contains statements or // expressions elseStart.reverseParentPathsTo(end); ifStart.addPathToChild(elseStart); } else if (ifStart.getIndex() == ifEnd.getIndex() && ifEnd.getIndex() != elseEnd.getIndex()) { // if only if-statement contains no expressions ifStart.addPathToChild(end); } else if (ifEnd.getIndex() == elseEnd.getIndex() && ifStart.getIndex() != ifEnd.getIndex()) { // if only else-statement contains no expressions ifStart.addPathToChild(end); } LOGGER.exiting(CLASS_NAME, "computeIf(3)"); } private void computeIf(int first, int last) { LOGGER.entering(CLASS_NAME, "computeIf(2)"); DataFlowNode ifStart = this.braceStack.get(first).getDataFlowNode(); DataFlowNode ifEnd = this.braceStack.get(last).getDataFlowNode(); // only if the if-statement contains another Statement or Expression if (ifStart.getIndex() != ifEnd.getIndex()) { DataFlowNode end = ifEnd.getFlow().get(ifEnd.getIndex() + 1); ifStart.addPathToChild(end); } LOGGER.exiting(CLASS_NAME, "computeIf(2)"); } /** * * @return formatted dump of the StackList */ private static String dump(String description, List<StackObject> stackList) { StringBuilder stringDump = new StringBuilder("Stack List ("); stringDump.append(description).append(") ListDump:\n"); for (StackObject stackObject : stackList) { stringDump.append('\n').append(stackObject.toString()); } return stringDump.toString(); } }