src.main.java.com.mebigfatguy.fbcontrib.detect.CustomBuiltXML Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fb-contrib Show documentation
Show all versions of fb-contrib Show documentation
An auxiliary findbugs.sourceforge.net plugin for java bug detectors that fall outside the narrow scope of detectors to be packaged with the product itself.
/*
* fb-contrib - Auxiliary detectors for Java programs
* Copyright (C) 2005-2018 Dave Brosius
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.mebigfatguy.fbcontrib.detect;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.bcel.classfile.Code;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.ToString;
import com.mebigfatguy.fbcontrib.utils.UnmodifiableList;
import com.mebigfatguy.fbcontrib.utils.Values;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.ba.ClassContext;
/**
* looks for methods that build xml based strings by concatenation strings and custom values together. Doing so makes brittle code, that is difficult to modify,
* validate and understand. It is cleaner to create external xml files that are transformed at runtime, using parameters set through Transformer.setParameter.
*/
public class CustomBuiltXML extends BytecodeScanningDetector {
private static final List xmlPatterns = UnmodifiableList.create(
// @formatter:off
new XMLPattern(Pattern.compile(".*<[a-zA-Z_](\\w)*>[^=]?.*"), true),
new XMLPattern(Pattern.compile(".*[a-zA-Z_](\\w)*>[^=]?.*"), true),
new XMLPattern(Pattern.compile(".*<[a-zA-Z_](\\w)*/>[^=]?.*"), true),
new XMLPattern(Pattern.compile(".*<[^=]?(/)?$"), true),
new XMLPattern(Pattern.compile("^(/)?>.*"), true),
new XMLPattern(Pattern.compile(".*=(\\s)*[\"'].*"), false),
new XMLPattern(Pattern.compile("^[\"']>.*"), true),
new XMLPattern(Pattern.compile(".*.*"), true),
new XMLPattern(Pattern.compile(".*xmlns:.*"), true)
// @formatter:on
);
private static final String CBX_MIN_REPORTABLE_ITEMS = "fb-contrib.cbx.minxmlitems";
/**
* This builder can be reused with different return types to reduce object creation,
* provided that param types are unchanged.
*/
private static final SignatureBuilder XML_SIG_BUILDER = new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING);
private BugReporter bugReporter;
private OpcodeStack stack;
private int xmlItemCount = 0;
private int xmlConfidentCount = 0;
private int lowReportingThreshold;
private int midReportingThreshold;
private int highReportingThreshold;
private int firstPC;
/**
* constructs a CBX detector given the reporter to report bugs on
*
* @param bugReporter
* the sync of bug reports
*/
public CustomBuiltXML(BugReporter bugReporter) {
this.bugReporter = bugReporter;
lowReportingThreshold = Integer.getInteger(CBX_MIN_REPORTABLE_ITEMS, 5).intValue();
midReportingThreshold = lowReportingThreshold << 1;
highReportingThreshold = lowReportingThreshold << 2;
}
/**
* overrides the visitor to create and destroy the stack
*
* @param classContext
* the context object of the currently parsed class
*/
@Override
public void visitClassContext(ClassContext classContext) {
try {
stack = new OpcodeStack();
super.visitClassContext(classContext);
} finally {
stack = null;
}
}
/**
* overrides the visitor reset the opcode stack
*
* @param obj
* the code object of the currently parsed method
*/
@Override
public void visitCode(Code obj) {
stack.resetForMethodEntry(this);
xmlItemCount = 0;
xmlConfidentCount = 0;
firstPC = -1;
super.visitCode(obj);
if ((xmlItemCount >= lowReportingThreshold) && (xmlConfidentCount > (lowReportingThreshold >> 1))) {
bugReporter.reportBug(new BugInstance(this, "CBX_CUSTOM_BUILT_XML",
(xmlItemCount >= highReportingThreshold) ? HIGH_PRIORITY : (xmlItemCount >= midReportingThreshold) ? NORMAL_PRIORITY : LOW_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this, firstPC));
}
}
/**
* overrides the visitor to find String concatenations including xml strings
*
* @param seen
* the opcode that is being visited
*/
@Override
public void sawOpcode(int seen) {
String strCon = null;
try {
stack.precomputation(this);
if (seen == INVOKESPECIAL) {
String clsName = getClassConstantOperand();
if (SignatureUtils.isPlainStringConvertableClass(clsName)) {
String methodName = getNameConstantOperand();
String methodSig = getSigConstantOperand();
if (Values.CONSTRUCTOR.equals(methodName) && XML_SIG_BUILDER.withReturnType(clsName).toString().equals(methodSig) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item itm = stack.getStackItem(0);
strCon = (String) itm.getConstant();
}
}
} else if (seen == INVOKEVIRTUAL) {
String clsName = getClassConstantOperand();
if (SignatureUtils.isPlainStringConvertableClass(clsName)) {
String methodName = getNameConstantOperand();
String methodSig = getSigConstantOperand();
if ("append".equals(methodName) && XML_SIG_BUILDER.withReturnType(clsName).toString().equals(methodSig) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item itm = stack.getStackItem(0);
strCon = (String) itm.getConstant();
}
}
}
if (strCon != null) {
strCon = strCon.trim();
if (strCon.length() == 0) {
return;
}
for (XMLPattern pattern : xmlPatterns) {
Matcher m = pattern.getPattern().matcher(strCon);
if (m.matches()) {
xmlItemCount++;
if (pattern.isConfident()) {
xmlConfidentCount++;
}
if ((firstPC < 0) && (xmlConfidentCount > 0)) {
firstPC = getPC();
}
break;
}
}
}
} finally {
stack.sawOpcode(this, seen);
}
}
/**
* represents a text pattern that is likely to be an xml snippet, as well as how much confidence that the pattern is infact xml, versus something else.
*/
private static class XMLPattern {
private Pattern pattern;
private boolean confident;
public XMLPattern(Pattern p, boolean isConfident) {
pattern = p;
confident = isConfident;
}
public Pattern getPattern() {
return pattern;
}
public boolean isConfident() {
return confident;
}
@Override
public String toString() {
return ToString.build(this);
}
}
}