com.cflint.CFLint Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of CFLint Show documentation
Show all versions of CFLint Show documentation
A static code analysis tool for ColdFusion (in the spirit of FindBugs and Lint). With CFLint, you are able to analyze your ColdFusion code base for code violations.
package com.cflint;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.cflint.config.*;
import org.antlr.runtime.BitSet;
import org.antlr.v4.runtime.ANTLRErrorListener;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.atn.ATNConfigSet;
import org.antlr.v4.runtime.dfa.DFA;
import com.cflint.BugInfo.BugInfoBuilder;
import com.cflint.config.CFLintPluginInfo.PluginInfoRule;
import com.cflint.config.CFLintPluginInfo.PluginInfoRule.PluginMessage;
import com.cflint.listeners.ScanProgressListener;
import com.cflint.plugins.CFLintScanner;
import com.cflint.plugins.CFLintSet;
import com.cflint.plugins.CFLintStructureListener;
import com.cflint.plugins.Context;
import com.cflint.plugins.Context.ContextMessage;
import com.cflint.plugins.Context.ContextType;
import com.cflint.plugins.core.CFScopes;
import com.cflint.plugins.exceptions.CFLintExceptionListener;
import com.cflint.plugins.exceptions.DefaultCFLintExceptionListener;
import com.cflint.tools.AllowedExtensionsLoader;
import com.cflint.tools.CFLintFilter;
import com.cflint.tools.CommentReformatting;
import com.cflint.tools.FileUtil;
import com.cflint.tools.PrecedingCommentReader;
import com.cflint.tools.ScanningProgressMonitorLookAhead;
import cfml.CFSCRIPTLexer;
import cfml.CFSCRIPTParser;
import cfml.parsing.CFMLParser;
import cfml.parsing.CFMLSource;
import cfml.parsing.ParserTag;
import cfml.parsing.cfscript.CFArrayExpression;
import cfml.parsing.cfscript.CFAssignmentExpression;
import cfml.parsing.cfscript.CFExpression;
import cfml.parsing.cfscript.CFFullVarExpression;
import cfml.parsing.cfscript.CFFunctionExpression;
import cfml.parsing.cfscript.CFIdentifier;
import cfml.parsing.cfscript.CFLiteral;
import cfml.parsing.cfscript.CFMember;
import cfml.parsing.cfscript.CFStatement;
import cfml.parsing.cfscript.CFStringExpression;
import cfml.parsing.cfscript.CFTernaryExpression;
import cfml.parsing.cfscript.CFVarDeclExpression;
import cfml.parsing.cfscript.script.CFCase;
import cfml.parsing.cfscript.script.CFCatchStatement;
import cfml.parsing.cfscript.script.CFCompDeclStatement;
import cfml.parsing.cfscript.script.CFCompoundStatement;
import cfml.parsing.cfscript.script.CFExpressionStatement;
import cfml.parsing.cfscript.script.CFForInStatement;
import cfml.parsing.cfscript.script.CFForStatement;
import cfml.parsing.cfscript.script.CFFuncDeclStatement;
import cfml.parsing.cfscript.script.CFFunctionParameter;
import cfml.parsing.cfscript.script.CFIfStatement;
import cfml.parsing.cfscript.script.CFIncludeStatement;
import cfml.parsing.cfscript.script.CFParsedStatement;
import cfml.parsing.cfscript.script.CFPropertyStatement;
import cfml.parsing.cfscript.script.CFReturnStatement;
import cfml.parsing.cfscript.script.CFScriptStatement;
import cfml.parsing.cfscript.script.CFSwitchStatement;
import cfml.parsing.cfscript.script.CFTryCatchStatement;
import cfml.parsing.reporting.ArrayErrorListener;
import cfml.parsing.reporting.IErrorReporter;
import cfml.parsing.reporting.ParseException;
import net.htmlparser.jericho.Attribute;
import net.htmlparser.jericho.Element;
import net.htmlparser.jericho.EndTag;
import net.htmlparser.jericho.Source;
public class CFLint implements IErrorReporter {
static final String FILE_ERROR = "FILE_ERROR";
static final String PARSE_ERROR = "PARSE_ERROR";
static final String PLUGIN_ERROR = "PLUGIN_ERROR";
static final String RESOURCE_BUNDLE_NAME = "com.cflint.cflint";
CFMLParser cfmlParser = new CFMLParser();
StackHandler handler = new StackHandler();
BugList bugs;
final List extensions = new ArrayList();
final List allowedExtensions = new ArrayList();
boolean verbose = false;
boolean logError = false;
boolean quiet = false;
boolean showProgress = false;
boolean progressUsesThread = true;
CFLintStats stats = new CFLintStats();
// constants
public CFLintStats getStats() {
return stats;
}
final List scanProgressListeners = new ArrayList();
final List exceptionListeners = new ArrayList();
CFLintConfiguration configuration;
// Stack to store include file depth to ensure no recursion
final Stack includeFileStack = new Stack();
public CFLint(final CFLintConfiguration configFile) throws IOException {
configuration = configFile == null ? new CFLintConfig() : configFile;
for (final PluginInfoRule ruleInfo : configuration.getRules()) {
addScanner(ConfigUtils.loadPlugin(ruleInfo));// TODO load them all
}
final CFLintFilter filter = CFLintFilter.createFilter(verbose);
bugs = new BugList(filter);
if (exceptionListeners.isEmpty()) {
addExceptionListener(new DefaultCFLintExceptionListener(bugs));
}
allowedExtensions.addAll(AllowedExtensionsLoader.init(RESOURCE_BUNDLE_NAME));
cfmlParser.setErrorReporter(this);
}
@Deprecated
public CFLint(final CFLintConfiguration configuration, final CFLintScanner... bugsScanners) {
super();
this.configuration = configuration;
for (final CFLintScanner scanner : bugsScanners) {
extensions.add(scanner);
if (configuration != null) {
final PluginInfoRule ruleInfo = configuration.getRuleByClass(scanner.getClass());
if (ruleInfo != null) {
ruleInfo.setPluginInstance(scanner);
}
}
if(scanner instanceof CFLintSet){
((CFLintSet)scanner).setCFLint(this);
}
}
CFLintFilter filter;
try {
filter = CFLintFilter.createFilter(verbose);
bugs = new BugList(filter);
} catch (final IOException e1) {
e1.printStackTrace();
}
if (exceptionListeners.isEmpty()) {
addExceptionListener(new DefaultCFLintExceptionListener(bugs));
}
allowedExtensions.addAll(AllowedExtensionsLoader.init(RESOURCE_BUNDLE_NAME));
cfmlParser.setErrorReporter(this);
}
public void scan(final String folderName) {
if (showProgress) {
ScanningProgressMonitorLookAhead.createInstance(this, folderName, progressUsesThread).startPreScan();
}
final File starterFile = new File(folderName);
setupConfigAncestry(starterFile.getParentFile());
scan(starterFile);
fireClose();
}
private void setupConfigAncestry(File folder){
Stack configFiles = new Stack();
fileLoop:
while(folder != null && folder.exists()){
for (final File file : folder.listFiles()) {
if (file.getName().toLowerCase().startsWith(".cflintrc")) {
if (verbose) {
System.out.println("read config " + file);
}
System.out.println(
"DEPRECATED: The uses of \"inheritPlugins\" and \"output\" have been marked as deprecated in CFLint 1.2.x and support for them will be fully removed in CFLint 1.3.0. Please remove the settings from your configuration file(s). Run CFLint in verbose mode for config file location details.");
try {
CFLintConfig newConfig = file.getName().toLowerCase().endsWith(".xml")
? ConfigUtils.unmarshal(new FileInputStream(file), CFLintConfig.class)
: ConfigUtils.unmarshalJson(new FileInputStream(file), CFLintConfig.class);
configFiles.push(newConfig);
if (!newConfig.isInheritParent()) {
break fileLoop;
}
} catch (Exception e) {
System.err.println("Could not read config file " + file);
}
}
}
folder = folder.getParentFile();
}
for(CFLintConfig newConfig: configFiles){
configuration = new CFLintChainedConfig(newConfig,configuration);
}
}
public void scan(final File folderOrFile) {
if (getBugs().getFileFilter() != null && !getBugs().getFileFilter().includeFile(folderOrFile)) {
return;
}
if(!folderOrFile.exists()){
System.err.println("File " + folderOrFile + " does not exist.");
return;
}
if (folderOrFile.isDirectory()) {
final CFLintConfiguration saveConfig = configuration;
try {
for (final File file : folderOrFile.listFiles()) {
if (file.getName().toLowerCase().startsWith(".cflintrc")) {
try {
if(verbose){
System.out.println("read config " + file);
}
System.out.println("DEPRECATED: The use of \"inheritPlugins\" has been marked as deprecated in CFLint 1.2.x and support for it will be fully removed in CFLint 1.3.0. Please remove the setting from your configuration file(s). Run CFLint in verbose mode for config file location details.");
CFLintConfiguration newConfig = file.getName().toLowerCase().endsWith(".xml") ?
ConfigUtils.unmarshal(new FileInputStream(file), CFLintConfig.class) :
ConfigUtils.unmarshalJson(new FileInputStream(file), CFLintConfig.class);
configuration = new CFLintChainedConfig(newConfig, configuration);
} catch (Exception e) {
System.err.println("Could not read config file " + file);
}
}
}
for (final File file : folderOrFile.listFiles()) {
scan(file);
}
} finally {
configuration = saveConfig;
}
} else if (!folderOrFile.isHidden() && FileUtil.checkExtension(folderOrFile, allowedExtensions)) {
final String src = FileUtil.loadFile(folderOrFile);
includeFileStack.clear();
try {
//Report number of lines in the source
stats.addFile(src==null||src.length()==0?0:src.split("\\R").length + 1);
process(src, folderOrFile.getAbsolutePath());
} catch (final Exception e) {
printException(e);
if (logError) {
System.out.println("Logging Error: " + FILE_ERROR);
fireCFLintException(e, FILE_ERROR, folderOrFile.getAbsolutePath(), null, null, null, null);
}
}
}
}
protected void printException(final Exception e, Element... elem) {
if (!quiet) {
if (elem != null && elem.length > 0 && elem[0] != null) {
final int line = elem[0].getSource().getRow(elem[0].getBegin());
System.err.println("Error in: " + shortSource(elem[0].getSource(), line) + " @ " + line + ":");
}
if (verbose) {
e.printStackTrace(System.err);
} else {
System.err.println(e.getMessage());
}
}
}
public void process(final String src, final String filename) throws ParseException, IOException {
fireStartedProcessing(filename);
if(src==null || src.trim().length() == 0){
final Context context = new Context(filename, null, null, false, handler);
reportRule(null, null, context,null, new ContextMessage("AVOID_EMPTY_FILES", null));
}else{
final CFMLSource cfmlSource = new CFMLSource(
src.contains(" in the tag hierarchy
* @param element The element to process
* @param msgcode The message code to check for
* @return
*/
protected boolean checkForDisabled(final Element element, final String msgcode) {
Element elem = element;
while (elem != null) {
final Element prevSibling = getPreviousSibling(elem);
if (prevSibling != null && prevSibling.getName().equals("!---")) {
final Pattern p = Pattern.compile(".*---\\s*CFLINT-DISABLE\\s+(.*)\\s*---.*");
final Matcher m = p.matcher(prevSibling.toString().toUpperCase().trim());
if (m.matches()) {
// No message codes in CFLINT-DISABLE
if (m.group(1).trim().length() == 0) {
if (verbose) {
System.out.println("Skipping disabled " + msgcode);
}
return true;
}
// check for matching message codes in CFLINT-DISABLE
for (String skipcode : m.group(1).split(",")) {
skipcode = skipcode.trim();
if (msgcode.equals(skipcode)) {
if (verbose) {
System.out.println("Skipping disabled " + msgcode);
}
return true;
}
}
}
}
elem = elem.getParentElement();
}
return false;
}
public void reportRule(final Element elem, final Object expression, final Context context,
final CFLintScanner pluginParm, final ContextMessage msg) {
//If we are processing includes, do NOT report any errors
if(!includeFileStack.isEmpty()){
return;
}
final String msgcode = msg.getMessageCode();
final String nameVar = msg.getVariable();
final CFLintScanner plugin = msg.getSource() == null ? pluginParm : msg.getSource();
if (checkForDisabled(elem, msgcode)) {
return;
}
if (configuration == null) {
throw new NullPointerException("Configuration is null");
}
PluginInfoRule ruleInfo;
if ("MISSING_SEMI".equals(msgcode)) {
ruleInfo = new PluginInfoRule();
final PluginMessage msgInfo = new PluginMessage("MISSING_SEMI");
msgInfo.setMessageText("End of statement(;) expected instead of ${variable}");
msgInfo.setSeverity("ERROR");
ruleInfo.getMessages().add(msgInfo);
}else if ("PLUGIN_ERROR".equals(msgcode)) {
ruleInfo = new PluginInfoRule();
final PluginMessage msgInfo = new PluginMessage("PLUGIN_ERROR");
msgInfo.setMessageText("Error in plugin: ${variable}");
msgInfo.setSeverity("ERROR");
ruleInfo.getMessages().add(msgInfo);
}else if ("AVOID_EMPTY_FILES".equals(msgcode)) {
ruleInfo = new PluginInfoRule();
final PluginMessage msgInfo = new PluginMessage("AVOID_EMPTY_FILES");
msgInfo.setMessageText("CF file is empty: ${file}");
msgInfo.setSeverity("WARNING");
ruleInfo.getMessages().add(msgInfo);
}else if ("PARSE_ERROR".equals(msgcode)) {
ruleInfo = new CFLintPluginInfo.PluginInfoRule();
final CFLintPluginInfo.PluginInfoRule.PluginMessage msgInfo = new CFLintPluginInfo.PluginInfoRule.PluginMessage("PARSE_ERROR");
msgInfo.setMessageText("Unable to parse");
msgInfo.setSeverity("ERROR");
ruleInfo.getMessages().add(msgInfo);
} else {
if (plugin == null) {
throw new NullPointerException(
"Plugin not set. Plugin should be using addMessage(messageCode,variable,source) to report messages in parent contexts");
}
ruleInfo = configuration.getRuleForPlugin(plugin);
}
if (ruleInfo == null) {
throw new NullPointerException("Rule not found for " + plugin.getClass().getSimpleName());
}
final PluginMessage msgInfo = ruleInfo.getMessageByCode(msgcode);
if (configuration == null) {
throw new NullPointerException(
"Message definition not found for [" + msgcode + "] in " + plugin.getClass().getSimpleName());
}
final BugInfoBuilder bldr = new BugInfo.BugInfoBuilder().setMessageCode(msgcode).setVariable(nameVar)
.setFunction(context.getFunctionName()).setFilename(context.getFilename())
.setComponent(context.getComponentName());
if (msgInfo != null) {
bldr.setSeverity(msgInfo.getSeverity());
bldr.setMessage(msgInfo.getMessageText());
} else {
String errMessage = "Message code: " + msgcode + " is not configured correctly.";
fireCFLintException(new NullPointerException(errMessage), PLUGIN_ERROR, "", null, null, null, null);
bldr.setSeverity("WARNING");
bldr.setMessage(msgcode);
}
if (expression instanceof CFStatement) {
bldr.setExpression(((CFStatement) expression).Decompile(0));
} else if (expression instanceof CFScriptStatement) {
bldr.setExpression(((CFScriptStatement) expression).Decompile(0));
} else if (elem != null) {
bldr.setExpression(elem.toString());
}
bldr.setRuleParameters(ruleInfo.getParameters());
if (configuration.includes(ruleInfo.getMessageByCode(msgcode))
&& !configuration.excludes(ruleInfo.getMessageByCode(msgcode))) {
if (expression instanceof CFExpression) {
BugInfo bugInfo = bldr.build((CFExpression) expression, elem);
final Token token = ((CFExpression) expression).getToken();
if (!suppressed(bugInfo, token, context)) {
bugs.add(bugInfo);
}
} else {
final BugInfo bug = bldr.build((CFParsedStatement) expression, elem);
if (msg.getLine() != null) {
bug.setLine(msg.getLine());
bug.setColumn(0);
}
if (context != null && !context.isSuppressed(bug)) {
bugs.add(bug);
}
}
}
}
/*
* Look for a suppress comment on the same line. cflint:line - suppresses
* any messages on the same line cflint:MESSAGE_CODE - suppresses any
* message matching that code
*/
protected boolean suppressed(BugInfo bugInfo, Token token, Context context) {
if (context == null || context.isSuppressed(bugInfo))
return true;
Iterable tokens = context.afterTokens(token);
for (Token currentTok : tokens) {
if (currentTok.getLine() != token.getLine()) {
break;
}
if (currentTok.getChannel() == Token.HIDDEN_CHANNEL && currentTok.getType() == CFSCRIPTLexer.LINE_COMMENT) {
final String commentText = currentTok.getText().replaceFirst("^//\\s*", "").trim();
if (commentText.startsWith("cflint ")) {
Pattern pattern = Pattern.compile("cflint\\s+ignore:([\\w,]+).*");
Matcher matcher = pattern.matcher(commentText);
if (matcher.matches() && matcher.groupCount() > 0) {
final String ignoreCodes = matcher.group(1);
if (ignoreCodes.equalsIgnoreCase("line")) {
return true;
}
for (final String ignoreCode : ignoreCodes.split(",\\s*")) {
if (ignoreCode.equals(bugInfo.getMessageCode())) {
return true;
}
}
}
}
}
}
return false;
}
public BugList getBugs() {
return bugs;
}
public List getAllowedExtensions() {
return allowedExtensions;
}
public void setAllowedExtensions(final List allowedExtensions) {
this.allowedExtensions.clear();
this.allowedExtensions.addAll(allowedExtensions);
}
@Override
public void reportError(final String arg0) {
//Empty implementation
}
public void setVerbose(final boolean verbose) {
this.verbose = verbose;
}
public void setLogError(final boolean logError) {
this.logError = logError;
}
public void setQuiet(final boolean quiet) {
this.quiet = quiet;
}
public void addScanProgressListener(final ScanProgressListener scanProgressListener) {
scanProgressListeners.add(scanProgressListener);
}
protected void fireStartedProcessing(final String srcidentifier) {
cfmlParser = new CFMLParser();
cfmlParser.setErrorReporter(this);
currentFile = srcidentifier;
currentElement = null;
for (final CFLintStructureListener structurePlugin : getStructureListeners(extensions)) {
try {
structurePlugin.startFile(srcidentifier, bugs);
} catch (final Exception e) {
printException(e);
final Context context = new Context(currentFile,currentElement,null,true,null,null);
final ContextMessage cm = new ContextMessage("PARSE_ERROR", null,null,context.startLine());
reportRule(currentElement,null,context,null, cm);
}
}
for (final ScanProgressListener p : scanProgressListeners) {
p.startedProcessing(srcidentifier);
}
}
protected void fireFinishedProcessing(final String srcidentifier) {
for (final CFLintStructureListener structurePlugin : getStructureListeners(extensions)) {
try {
structurePlugin.endFile(srcidentifier, bugs);
} catch (final Exception e) {
printException(e);
final Context context = new Context(currentFile,currentElement,null,true,null,null);
final ContextMessage cm = new ContextMessage("PARSE_ERROR", null,null,context.startLine());
reportRule(currentElement,null,context,null, cm);
}
}
for (final ScanProgressListener p : scanProgressListeners) {
p.finishedProcessing(srcidentifier);
}
processed.clear();
}
protected void fireClose() {
for (final ScanProgressListener p : scanProgressListeners) {
p.close();
}
}
public void addScanner(final CFLintScanner plugin) {
if (plugin != null){
extensions.add(plugin);
if(plugin instanceof CFLintSet){
((CFLintSet)plugin).setCFLint(this);
}
}
}
public List getScanners() {
return extensions;
}
public void addExceptionListener(final CFLintExceptionListener exceptionListener) {
exceptionListeners.add(exceptionListener);
}
protected void fireCFLintException(final Throwable e, final String messageCode, final String filename,
final Integer line, final Integer column, final String functionName, final String expression) {
for (final CFLintExceptionListener p : exceptionListeners) {
p.exceptionOccurred(e, messageCode, filename, line, column, functionName, expression);
}
}
public void setShowProgress(final boolean showProgress) {
this.showProgress = showProgress;
}
public void setProgressUsesThread(final boolean progressUsesThread) {
this.progressUsesThread = progressUsesThread;
}
String currentFile = null;
Element currentElement = null;
private boolean strictInclude;
@Override
public void syntaxError(final Recognizer, ?> recognizer, final Object offendingSymbol, int line,
int charPositionInLine, final String msg, final org.antlr.v4.runtime.RecognitionException e) {
String expression = null;
if (offendingSymbol instanceof Token) {
expression = ((Token) offendingSymbol).getText();
if (expression.length() > 50) {
expression = expression.substring(1, 40) + "...";
}
}
if (currentElement != null) {
if (line == 1) {
line = currentElement.getSource().getRow(currentElement.getBegin());
charPositionInLine = charPositionInLine
+ currentElement.getSource().getColumn(currentElement.getBegin());
} else {
line = currentElement.getSource().getRow(currentElement.getBegin()) + line - 1;
}
}
if (recognizer instanceof Parser && ((Parser) recognizer).isExpectedToken(CFSCRIPTParser.SEMICOLON)) {
final Context context = new Context(currentFile,currentElement,null,true,null,null);
final ContextMessage cm = new ContextMessage("MISSING_SEMI", expression,null,line);
reportRule(currentElement,null,context,null, cm);
} else {
final Context context = new Context(currentFile,currentElement,null,true,null,null);
final ContextMessage cm = new ContextMessage("PARSE_ERROR", expression,null,line);
reportRule(currentElement,null,context,null, cm);
}
}
@Override
public void reportAmbiguity(final Parser recognizer, final DFA dfa, final int startIndex, final int stopIndex,
final boolean exact, final java.util.BitSet ambigAlts, final ATNConfigSet configs) {
//Empty implementation
}
@Override
public void reportAttemptingFullContext(final Parser recognizer, final DFA dfa, final int startIndex,
final int stopIndex, final java.util.BitSet conflictingAlts, final ATNConfigSet configs) {
//Empty implementation
}
@Override
public void reportContextSensitivity(final Parser recognizer, final DFA dfa, final int startIndex,
final int stopIndex, final int prediction, final ATNConfigSet configs) {
//Empty implementation
}
@Override
public void reportError(final org.antlr.v4.runtime.RecognitionException re) {
//Empty implementation
}
@Override
public void reportError(final String[] tokenNames, final org.antlr.v4.runtime.RecognitionException re) {
//Empty implementation
}
@Override
public void reportError(final org.antlr.v4.runtime.IntStream input,
final org.antlr.v4.runtime.RecognitionException re, final BitSet follow) {
//Empty implementation
}
public void setStrictIncludes(boolean strictInclude) {
this.strictInclude=strictInclude;
}
}