Skip to content

Commit

Permalink
Merge pull request #99 from cflint/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
ryaneberly committed Oct 12, 2015
2 parents ee52037 + 089d360 commit f0413f8
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.cflint.plugins.core;

import net.htmlparser.jericho.Element;
import cfml.parsing.cfscript.CFExpression;
import cfml.parsing.cfscript.CFBinaryExpression;
import cfml.parsing.cfscript.script.CFScriptStatement;
import cfml.parsing.cfscript.script.CFIfStatement;
import cfml.parsing.cfscript.script.CFExpressionStatement;

import com.cflint.BugInfo;
import com.cflint.BugList;
import com.cflint.plugins.CFLintScannerAdapter;
import com.cflint.plugins.Context;

public class BooleanExpressionChecker extends CFLintScannerAdapter {
final String severity = "INFO";

protected int lastLineNo = -1;

@Override
public void expression(final CFExpression expression, final Context context, final BugList bugs) {
if (expression instanceof CFBinaryExpression) {
String code = expression.Decompile(0).toLowerCase();

if (hasExplicitBooleanCheck(code)) {
int lineNo = ((CFBinaryExpression) expression).getLine() + context.startLine() - 1;

// Only report issue once per line
if (lastLineNo != lineNo) {
booleanExpression(lineNo, context, bugs);
lastLineNo = lineNo;
}
}
}
}

@Override
public void element(final Element element, final Context context, final BugList bugs) {
String tag = element.getName();

if (tag.equals("cfif")) {
String content = element.getStartTag().getTagContent().toString();

if (hasExplicitBooleanCheck(content)) {
int lineNo = element.getSource().getRow(element.getBegin());

// Only report issue once per line
if (lastLineNo != lineNo) {
booleanExpression(lineNo, context, bugs);
lastLineNo = lineNo;
}
}
}
}

protected boolean hasExplicitBooleanCheck(final String code) {
return code.contains("== true") || code.contains("eq true") || code.contains("is true") || code.contains("!= true")
|| code.contains("== false") || code.contains("eq false") || code.contains("is false") || code.contains("!= false");
}

public void booleanExpression(final int lineNo, final Context context, final BugList bugs) {
bugs.add(new BugInfo.BugInfoBuilder().setLine(lineNo).setMessageCode("EXPLICIT_BOOLEAN_CHECK")
.setSeverity(severity).setFilename(context.getFilename())
.setMessage("Explicit check of boolean expession at " + lineNo + " is not needed.")
.build());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.cflint.plugins.core;

import net.htmlparser.jericho.Element;
import cfml.parsing.cfscript.CFExpression;
import cfml.parsing.cfscript.CFBinaryExpression;
import cfml.parsing.cfscript.script.CFScriptStatement;
import cfml.parsing.cfscript.script.CFIfStatement;
import cfml.parsing.cfscript.script.CFExpressionStatement;

import com.cflint.BugInfo;
import com.cflint.BugList;
import com.cflint.plugins.CFLintScannerAdapter;
import com.cflint.plugins.Context;

public class ComplexBooleanExpressionChecker extends CFLintScannerAdapter {
final String severity = "WARNING";

protected int complexThreshold = 10;

@Override
public void expression(final CFExpression expression, final Context context, final BugList bugs) {
if (expression instanceof CFBinaryExpression) {
String code = expression.Decompile(0).toLowerCase();

if (isComplex(code, complexThreshold)) {
int lineNo = ((CFBinaryExpression) expression).getLine() + context.startLine() - 1;

complexBooleanExpression(lineNo, context, bugs);
}
}
}

@Override
public void element(final Element element, final Context context, final BugList bugs) {
String tag = element.getName();

if (tag.equals("cfif")) {
String content = element.getStartTag().getTagContent().toString();

if (isComplex(content, complexThreshold)) {
int lineNo = element.getSource().getRow(element.getBegin());

complexBooleanExpression(lineNo, context, bugs);
}
}
}

protected boolean isComplex(final String code, final int complexThreshold) {
int noAnds = noSubstrings(code, " && ") + noSubstrings(code, " and ");
int noOrs = noSubstrings(code, " || ") + noSubstrings(code, " or ");
int noNots = noSubstrings(code, " ! ") + noSubstrings(code, " not ");

// This is just a rough heuristic
// lots of and's or or's on their own is usually easy to understand
// but combine them together and it gets hard to understand very quickly
return (noAnds * noOrs + noNots + noAnds + noOrs) > complexThreshold;
}

protected int noSubstrings(String string, String substring) {
int lastIndex = 0;
int count = 0;

while (lastIndex != -1) {
lastIndex = string.indexOf(substring, lastIndex);

if (lastIndex != -1) {
count ++;
lastIndex += substring.length();
}
}

return count;
}

public void complexBooleanExpression(final int lineNo, final Context context, final BugList bugs) {
bugs.add(new BugInfo.BugInfoBuilder().setLine(lineNo).setMessageCode("COMPLEX_BOOLEAN_CHECK")
.setSeverity(severity).setFilename(context.getFilename())
.setMessage("Boolean expession at " + lineNo + " is too complex. Consider simplifying or moving to a named method.")
.build());
}

}
10 changes: 10 additions & 0 deletions src/main/resources/cflint.definition.xml
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,14 @@
<severity>INFO</severity>
</message>
</ruleImpl>
<ruleImpl name="ComplexBooleanExpressionChecker" className="ComplexBooleanExpressionChecker">
<message code="COMPLEX_BOOLEAN_CHECK">
<severity>WARNING</severity>
</message>
</ruleImpl>
<ruleImpl name="BooleanExpressionChecker" className="BooleanExpressionChecker">
<message code="EXPLICIT_BOOLEAN_CHECK">
<severity>INFO</severity>
</message>
</ruleImpl>
</CFLint-Plugin>
65 changes: 65 additions & 0 deletions src/test/java/com/cflint/TestBooleanExpressionChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.cflint;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import cfml.parsing.reporting.ParseException;

import com.cflint.config.CFLintPluginInfo.PluginInfoRule;
import com.cflint.config.CFLintPluginInfo.PluginInfoRule.PluginMessage;
import com.cflint.config.ConfigRuntime;
import com.cflint.plugins.core.BooleanExpressionChecker;

public class TestBooleanExpressionChecker {

private CFLint cfBugs;

@Before
public void setUp() {
final ConfigRuntime conf = new ConfigRuntime();
final PluginInfoRule pluginRule = new PluginInfoRule();
pluginRule.setName("BooleanExpressionChecker");
conf.getRules().add(pluginRule);
final PluginMessage pluginMessage = new PluginMessage("EXPLICIT_BOOLEAN_CHECK");
pluginMessage.setSeverity("INFO");
cfBugs = new CFLint(conf, new BooleanExpressionChecker());
}

@Test
public void testBooleanExpressionInScript() throws ParseException, IOException {
final String scriptSrc = "<cfscript>\r\n"
+ "if (a && b == true) {\r\n"
+ " c = 1;\r\n"
+ "}\r\n"
+ "else if (a or b is false) {\r\n"
+ " c = 1;\r\n"
+ "}\r\n"
+ "</cfscript>";

cfBugs.process(scriptSrc, "test");
final List<BugInfo> result = cfBugs.getBugs().getBugList().values().iterator().next();
assertEquals(2, result.size());
assertEquals("EXPLICIT_BOOLEAN_CHECK", result.get(0).getMessageCode());
assertEquals(2, result.get(0).getLine());
assertEquals("EXPLICIT_BOOLEAN_CHECK", result.get(1).getMessageCode());
assertEquals(5, result.get(1).getLine());
}

@Test
public void testBooleanExpressionInTag() throws ParseException, IOException {
final String tagSrc = "<cfset a = 23>\r\n"
+ "<cfset a = not (b and c) is false>";

cfBugs.process(tagSrc, "test");
final List<BugInfo> result = cfBugs.getBugs().getBugList().values().iterator().next();
assertEquals(1, result.size());
assertEquals("EXPLICIT_BOOLEAN_CHECK", result.get(0).getMessageCode());
assertEquals(2, result.get(0).getLine());
}

}
65 changes: 65 additions & 0 deletions src/test/java/com/cflint/TestComplexBooleanExpressionChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.cflint;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import cfml.parsing.reporting.ParseException;

import com.cflint.config.CFLintPluginInfo.PluginInfoRule;
import com.cflint.config.CFLintPluginInfo.PluginInfoRule.PluginMessage;
import com.cflint.config.ConfigRuntime;
import com.cflint.plugins.core.ComplexBooleanExpressionChecker;

public class TestComplexBooleanExpressionChecker {

private CFLint cfBugs;

@Before
public void setUp() {
final ConfigRuntime conf = new ConfigRuntime();
final PluginInfoRule pluginRule = new PluginInfoRule();
pluginRule.setName("ComplexBooleanExpressionChecker");
conf.getRules().add(pluginRule);
final PluginMessage pluginMessage = new PluginMessage("COMPLEX_BOOLEAN_CHECK");
pluginMessage.setSeverity("WARNING");
cfBugs = new CFLint(conf, new ComplexBooleanExpressionChecker());
}

@Test
public void testBooleanExpressionInScript() throws ParseException, IOException {
final String scriptSrc = "<cfscript>\r\n"
+ "if (a && b || c && d || e && f) {\r\n"
+ " c = 1;\r\n"
+ "}\r\n"
+ "else if (a or not b and not a or b and not c) {\r\n"
+ " c = 1;\r\n"
+ "}\r\n"
+ "</cfscript>";

cfBugs.process(scriptSrc, "test");
final List<BugInfo> result = cfBugs.getBugs().getBugList().values().iterator().next();
assertEquals(2, result.size());
assertEquals("COMPLEX_BOOLEAN_CHECK", result.get(0).getMessageCode());
assertEquals(2, result.get(0).getLine());
assertEquals("COMPLEX_BOOLEAN_CHECK", result.get(1).getMessageCode());
assertEquals(5, result.get(1).getLine());
}

@Test
public void testBooleanExpressionInTag() throws ParseException, IOException {
final String tagSrc = "<cfset a = 23>\r\n"
+ "<cfset a = a and not b or c and d or not e and f>";

cfBugs.process(tagSrc, "test");
final List<BugInfo> result = cfBugs.getBugs().getBugList().values().iterator().next();
assertEquals(1, result.size());
assertEquals("COMPLEX_BOOLEAN_CHECK", result.get(0).getMessageCode());
assertEquals(2, result.get(0).getLine());
}

}

0 comments on commit f0413f8

Please sign in to comment.