/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.controversial;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.dfa.DataFlowNode;
import net.sourceforge.pmd.lang.dfa.VariableAccess;
import net.sourceforge.pmd.lang.dfa.pathfinder.CurrentPath;
import net.sourceforge.pmd.lang.dfa.pathfinder.DAAPathFinder;
import net.sourceforge.pmd.lang.dfa.pathfinder.Executable;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
/**
* Starts path search for each method and runs code if found.
*
* @author raik
* @author Sven Jacob
*/
public class DataflowAnomalyAnalysisRule extends AbstractJavaRule implements Executable {
private RuleContext rc;
private List<DaaRuleViolation> daaRuleViolations;
private int maxRuleViolations;
private int currentRuleViolationCount;
private static final IntegerProperty MAX_PATH_DESCRIPTOR = new IntegerProperty("maxPaths",
"Maximum number of checked paths per method. A lower value will increase the performance of the rule but may decrease anomalies found.",
100, 8000, 1000, 1.0f);
private static final IntegerProperty MAX_VIOLATIONS_DESCRIPTOR = new IntegerProperty("maxViolations",
"Maximum number of anomalies per class", 1, 2000, 100, 2.0f);
private static class Usage {
public int accessType;
public DataFlowNode node;
Usage(int accessType, DataFlowNode node) {
this.accessType = accessType;
this.node = node;
}
public String toString() {
return "accessType = " + accessType + ", line = " + node.getLine();
}
}
public DataflowAnomalyAnalysisRule() {
definePropertyDescriptor(MAX_PATH_DESCRIPTOR);
definePropertyDescriptor(MAX_VIOLATIONS_DESCRIPTOR);
}
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
maxRuleViolations = getProperty(MAX_VIOLATIONS_DESCRIPTOR);
currentRuleViolationCount = 0;
return super.visit(node, data);
}
public Object visit(ASTMethodDeclaration methodDeclaration, Object data) {
rc = (RuleContext) data;
daaRuleViolations = new ArrayList<>();
final DataFlowNode node = methodDeclaration.getDataFlowNode().getFlow().get(0);
final DAAPathFinder pathFinder = new DAAPathFinder(node, this, getProperty(MAX_PATH_DESCRIPTOR));
pathFinder.run();
super.visit(methodDeclaration, data);
return data;
}
public void execute(CurrentPath path) {
if (maxNumberOfViolationsReached()) {
return;
}
Map<String, Usage> usagesByVarName = new HashMap<>();
Iterator<DataFlowNode> pathIterator = path.iterator();
while (pathIterator.hasNext()) {
// iterate all nodes in this path
DataFlowNode inode = pathIterator.next();
if (inode.getVariableAccess() != null) {
// iterate all variables of this node
for (VariableAccess va : inode.getVariableAccess()) {
// get the last usage of the current variable
Usage lastUsage = usagesByVarName.get(va.getVariableName());
if (lastUsage != null) {
// there was a usage to this variable before
checkVariableAccess(inode, va, lastUsage);
}
Usage newUsage = new Usage(va.getAccessType(), inode);
// put the new usage for the variable
usagesByVarName.put(va.getVariableName(), newUsage);
}
}
}
}
private void checkVariableAccess(DataFlowNode inode, VariableAccess va, final Usage u) {
// get the start and end line
int startLine = u.node.getLine();
int endLine = inode.getLine();
Node lastNode = inode.getNode();
Node firstNode = u.node.getNode();
if (va.accessTypeMatches(u.accessType) && va.isDefinition()) { // DD
addDaaViolation(rc, lastNode, "DD", va.getVariableName(), startLine, endLine);
} else if (u.accessType == VariableAccess.UNDEFINITION && va.isReference()) { // UR
addDaaViolation(rc, lastNode, "UR", va.getVariableName(), startLine, endLine);
} else if (u.accessType == VariableAccess.DEFINITION && va.isUndefinition()) { // DU
addDaaViolation(rc, firstNode, "DU", va.getVariableName(), startLine, endLine);
}
}
/**
* Adds a daa violation to the report.
*/
private void addDaaViolation(Object data, Node node, String type, String var, int startLine, int endLine) {
if (!maxNumberOfViolationsReached() && !violationAlreadyExists(type, var, startLine, endLine) && node != null) {
RuleContext ctx = (RuleContext) data;
String msg = type;
if (getMessage() != null) {
msg = MessageFormat.format(getMessage(), type, var, startLine, endLine);
}
DaaRuleViolation violation = new DaaRuleViolation(this, ctx, node, type, msg, var, startLine, endLine);
ctx.getReport().addRuleViolation(violation);
daaRuleViolations.add(violation);
currentRuleViolationCount++;
}
}
/**
* Maximum number of violations was already reached?
*
* @return <code>true</code> if the maximum number of violations was
* reached, <code>false</code> otherwise.
*/
private boolean maxNumberOfViolationsReached() {
return currentRuleViolationCount >= maxRuleViolations;
}
/**
* Checks if a violation already exists. This is needed because on the
* different paths same anomalies can occur.
*
* @param type
* @param var
* @param startLine
* @param endLine
* @return true if the violation already was added to the report
*/
private boolean violationAlreadyExists(String type, String var, int startLine, int endLine) {
for (DaaRuleViolation violation : daaRuleViolations) {
if (violation.getBeginLine() == startLine && violation.getEndLine() == endLine
&& violation.getType().equals(type) && violation.getVariableName().equals(var)) {
return true;
}
}
return false;
}
}