Reused existing npath implementation

This commit is contained in:
oowekyala
2017-07-26 19:49:41 +02:00
parent 9c3a97f641
commit bef9e64021
3 changed files with 216 additions and 186 deletions

View File

@ -4,7 +4,6 @@
package net.sourceforge.pmd.lang.java.metrics.impl;
import static net.sourceforge.pmd.lang.java.metrics.impl.visitors.DefaultNpathVisitor.NpathDataNode;
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
import net.sourceforge.pmd.lang.java.metrics.impl.visitors.DefaultNpathVisitor;
@ -21,7 +20,6 @@ public class NpathMetric extends AbstractJavaOperationMetric {
@Override
public double computeFor(ASTMethodOrConstructorDeclaration node, MetricVersion version) {
NpathDataNode root = (NpathDataNode) node.jjtAccept(new DefaultNpathVisitor(), new NpathDataNode());
return root.getComplexity();
return (Integer) node.jjtAccept(new DefaultNpathVisitor(), null);
}
}

View File

@ -12,132 +12,183 @@ import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric;
/**
* Visitor for the default n-path complexity version. It takes a root {@link NpathDataNode} as data.
* Visitor for the default n-path complexity version.
*
* @author Clément Fournier
* @author Jason Bennett
*/
public class DefaultNpathVisitor extends JavaParserVisitorAdapter {
/* Multiplies the complexity of the children of this node. */
private int multiplyChildrenComplexities(JavaNode node, Object data) {
int product = 1;
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
JavaNode n = (JavaNode) node.jjtGetChild(i);
product *= (Integer) n.jjtAccept(this, data);
}
return product;
}
/* Sums the complexity of the children of the node. */
private int sumChildrenComplexities(JavaNode node, Object data) {
int sum = 0;
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
JavaNode n = (JavaNode) node.jjtGetChild(i);
sum += (Integer) n.jjtAccept(this, data);
}
return sum;
}
@Override
public Object visit(ASTMethodDeclaration node, Object data) {
return multiplyChildrenComplexities(node, data);
}
@Override
public Object visit(JavaNode node, Object data) {
return multiplyChildrenComplexities(node, data);
}
@Override
public Object visit(ASTIfStatement node, Object data) {
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
// (npath of if + npath of else (or 1) + bool_comp of if) * npath of next
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
super.visit(node, n);
return n.parent;
List<JavaNode> statementChildren = new ArrayList<>();
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
if (node.jjtGetChild(i) instanceof ASTStatement) {
statementChildren.add((JavaNode) node.jjtGetChild(i));
}
}
// add path for not taking if
int complexity = node.hasElse() ? 0 : 1;
for (JavaNode element : statementChildren) {
complexity += (Integer) element.jjtAccept(this, data);
}
int boolCompIf = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
return boolCompIf + complexity;
}
@Override
public Object visit(ASTWhileStatement node, Object data) {
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
// (npath of while + bool_comp of while + 1) * npath of next
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
super.visit(node, n);
return n.parent;
}
int boolCompWhile = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
int nPathWhile = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
@Override
public Object visit(ASTForStatement node, Object data) {
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
super.visit(node, n);
return n.parent;
return boolCompWhile + nPathWhile + 1;
}
@Override
public Object visit(ASTDoStatement node, Object data) {
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
// (npath of do + bool_comp of do + 1) * npath of next
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
super.visit(node, n);
return n.parent;
int boolCompDo = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
int nPathDo = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
return boolCompDo + nPathDo + 1;
}
@Override
public Object visit(ASTConditionalExpression node, Object data) {
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
public Object visit(ASTForStatement node, Object data) {
// (npath of for + bool_comp of for + 1) * npath of next
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
super.visit(node, n);
return n.parent;
int boolCompFor = CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class));
int nPathFor = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
return boolCompFor + nPathFor + 1;
}
@Override
public Object visit(ASTTryStatement node, Object data) {
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
public Object visit(ASTReturnStatement node, Object data) {
// return statements are valued at 1, or the value of the boolean expression
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
super.visit(node, n);
return n.parent;
ASTExpression expr = node.getFirstChildOfType(ASTExpression.class);
if (expr == null) {
return 1;
}
int boolCompReturn = CycloMetric.booleanExpressionComplexity(expr);
int conditionalExpressionComplexity = multiplyChildrenComplexities(expr, data);
if (conditionalExpressionComplexity > 1) {
boolCompReturn += conditionalExpressionComplexity;
}
return (boolCompReturn > 0) ? boolCompReturn : 1;
}
@Override
public Object visit(ASTSwitchStatement node, Object data) {
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
// bool_comp of switch + sum(npath(case_range))
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
super.visit(node, n);
return n.parent;
int boolCompSwitch = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
int npath = 0;
int caseRange = 0;
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
JavaNode n = (JavaNode) node.jjtGetChild(i);
// Fall-through labels count as 1 for complexity
if (n instanceof ASTSwitchLabel) {
npath += caseRange;
caseRange = 1;
} else {
Integer complexity = (Integer) n.jjtAccept(this, data);
caseRange *= complexity;
}
}
// add in npath of last label
npath += caseRange;
return boolCompSwitch + npath;
}
/**
* Heap structure used as the data of the visitor. Every node corresponds to a control flow statement. A control
* flow statement nested inside another is represented by a node that's a children of the other.
*
* <p>The complexity of a node is given by multiplying the complexities of its children plus 1. The complexity of a
* leaf is the boolean complexity of the guard condition of the statement. The root node bears the complexity of the
* method.
*/
public static class NpathDataNode {
private List<NpathDataNode> children = new ArrayList<>();
private NpathDataNode parent;
private int booleanComplexity;
@Override
public Object visit(ASTConditionalExpression node, Object data) {
return node.isTernary() ? sumChildrenComplexities(node, data) + 2 : 1;
}
/** Creates a root node. */
public NpathDataNode() {
}
private NpathDataNode(int booleanComplexity, NpathDataNode parent) {
this.booleanComplexity = booleanComplexity;
this.parent = parent;
}
NpathDataNode addAndGetChild(int booleanComplexity) {
NpathDataNode newChild = new NpathDataNode(booleanComplexity, this);
children.add(newChild);
return newChild;
}
/** Gets the complexity of this node. */
public int getComplexity() {
int complexity = 1 + booleanComplexity;
for (NpathDataNode child : children) {
complexity *= child.getComplexity();
}
return complexity;
}
@Override
public Object visit(ASTTryStatement node, Object data) {
/*
* This scenario was not addressed by the original paper. Based on the
* principles outlined in the paper, as well as the Checkstyle NPath
* implementation, this code will add the complexity of the try to the
* complexities of the catch and finally blocks.
*/
return sumChildrenComplexities(node, data);
}
}

View File

@ -3,40 +3,46 @@
<code-fragment id="full-example"><![CDATA[
public class Foo {
public static int bar() {
try{
if (true) {
public static void bar() {
boolean a, b = true;
try { // 2 * 2 + 2
if (true) { // 2
List buz = new ArrayList();
}
for(int i = 0; i < 19; i++) {
for(int i = 0; i < 19; i++) { // 2
List buz = new ArrayList();
}
int j = 0;
if (true) {
j = 10;
}
while (j++ < 20) {
List buz = new ArrayList();
}
if (true) {
j = 21;
}
if(false) {
j = 0;
}
do {
List buz = new ArrayList();
} while (j++ < 30);
} catch(Exception e) {
if (true) {
if (true) { // 2
e.printStackTrace();
}
}
if (true) {
return 1;
} else {
return 2;
int j = 0;
if (true || a && b) { // 4
j = 10;
return;
}
while (j++ < 20) { // 2
List buz = new ArrayList();
}
switch(j) { // 7
case 1:
case 2: break;
case 3: j = 5; break;
case 4: if (b && a) { bar(); } break;
default: break;
}
do { // 3
List buz = new ArrayList();
} while (a && j++ < 30);
if (b) { // 2
return;
}
}
}
@ -47,7 +53,7 @@ public class Foo {
<description>Full example</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.Foo#bar()' has value 10.</message>
<message>'.Foo#bar()' has value 2016.</message>
</expected-messages>
<code-ref id="full-example"/>
</test-code>
@ -130,17 +136,11 @@ public class Foo {
class Foo {
void bar() {
boolean a, b;
if (a) {
if (a) {}
else {}
} else {
}
if (b) {
} else {
}
if (b) {}
else {}
}
}
]]></code>
@ -156,15 +156,10 @@ public class Foo {
class Foo {
void bar() {
boolean a, b;
if (a) {
if (a) {}
else {}
} else {
}
if (b) {
}
if (b) {}
}
}
]]></code>
@ -180,95 +175,81 @@ public class Foo {
class Foo {
void bar() {
boolean a, b;
if (a) { // 3
if (b) {
if (a) {
if (b) {}
} else {}
}
} else {
}
if (b) { // times two
} else {
}
if (b) {}
else {}
}
}
]]></code>
</test-code>
<test-code>
<description>ok</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public static void bar() {
if (true) {List buz = new ArrayList();}
}
}
]]></code>
</test-code>
<test-code>
<description>fail, with minimum</description>
<rule-property name="reportLevel">1</rule-property>
<description>Full switch</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>The method 'bar()' has value 2</message>
<message>'.Foo#bar()' has value 7.</message>
</expected-messages>
<code><![CDATA[
public class Foo {
public static void bar() {
if (true) {List buz = new ArrayList();}
}
}
]]></code>
class Foo {
void bar() {
boolean a, b;
int j = 23;
switch(j) {
case 1:
case 2: break;
case 3: j = 5; break;
case 4: if (b && a) { bar(); } break;
default: break;
}
}
}
]]></code>
</test-code>
<test-code>
<description>Boolean operators</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.Foo#bar()' has value 4.</message>
</expected-messages>
<code><![CDATA[
class Foo {
void bar() {
if (a && b || c);
}
}
]]></code>
</test-code>
<code-fragment id="bug3484404"><![CDATA[
class Bar
{
public void x(boolean x, boolean y)
{
z(
(x ? 1 : 2),
(y ? 3 : 4)
);
}
public void x(boolean x, boolean y) {
z((x ? 1 : 2), (y ? 3 : 4));
}
public int y(boolean x, boolean y)
{
return z(
(x ? 1 : 2),
(y ? 3 : 4)
);
}
public int y(boolean x, boolean y) {
return z((x ? 1 : 2), (y ? 3 : 4));
}
public int z(int x, int y)
{
return x + y;
}
public int z(int x, int y) {
return x + y;
}
}
]]></code-fragment>
<test-code>
<description>test case for bug 3484404 (Invalid NPath calculation in return statement)</description>
<expected-problems>0</expected-problems>
<code-ref id="bug3484404"/>
</test-code>
<test-code>
<description>test case for bug 3484404 (Invalid NPath calculation in return statement) with minimum 25
</description>
<expected-problems>2</expected-problems>
<expected-problems>3</expected-problems>
<expected-messages>
<message>The method x() has an NPath complexity of 25</message>
<message>The method y() has an NPath complexity of 25</message>
<message>'.Bar#x(boolean, boolean)' has value 25.</message>
<message>'.Bar#y(boolean, boolean)' has value 25.</message>
<message>'.Bar#z(int, int)' has value 1.</message>
</expected-messages>
<code-ref id="bug3484404"/>
</test-code>