src.main.java.com.mebigfatguy.fbcontrib.detect.InconsistentKeyNameCasing 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.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nullable;
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.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.SourceLineAnnotation;
import edu.umd.cs.findbugs.ba.ClassContext;
/**
* looks for calls to HttpRequest.getParameter with parameters of the same name with different cases like 'id' and 'Id'.
*/
public class InconsistentKeyNameCasing extends BytecodeScanningDetector {
private static final String HTTP_SESSION = "javax/servlet/http/HttpSession";
private static final String HTTP_SERVLET_REQUEST = "javax/servlet/http/HttpServletRequest";
private static final String GET_ATTRIBUTE = "getAttribute";
private static final String SET_ATTRIBUTE = "setAttribute";
private static final String GET_PARAMETER = "getParameter";
private static final String GET_ATTRIBUTE_SIG = new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING)
.withReturnType(Values.SLASHED_JAVA_LANG_OBJECT).toString();
private static final String SET_ATTRIBUTE_SIG = SignatureBuilder.SIG_STRING_AND_OBJECT_TO_VOID;
private static final String GET_PARAMETER_SIG = new SignatureBuilder().withParamTypes(Values.SLASHED_JAVA_LANG_STRING)
.withReturnType(Values.SLASHED_JAVA_LANG_STRING).toString();
enum KeyType {
ATTRIBUTE("IKNC_INCONSISTENT_HTTP_ATTRIBUTE_CASING"), PARAMETER("IKNC_INCONSISTENT_HTTP_PARAM_CASING");
private String key;
KeyType(String descriptionKey) {
key = descriptionKey;
}
public String getDescription() {
return key;
}
}
BugReporter bugReporter;
OpcodeStack stack;
Map>>> parmInfo;
/**
* constructs a IKNC detector given the reporter to report bugs on
*
* @param reporter
* the sync of bug reports
*/
public InconsistentKeyNameCasing(BugReporter reporter) {
bugReporter = reporter;
parmInfo = new EnumMap<>(KeyType.class);
parmInfo.put(KeyType.ATTRIBUTE, new HashMap>>());
parmInfo.put(KeyType.PARAMETER, new HashMap>>());
}
/**
* implements the visitor to create the opcode 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;
}
}
/**
* implements the visitor to reset the opcode stack for a new method
*
* @param obj
* the context object of the currently parsed code block
*/
@Override
public void visitCode(Code obj) {
stack.resetForMethodEntry(this);
super.visitCode(obj);
}
/**
* implements the visitor to look for calls to HttpServletRequest.getParameter and collect what the name of the key is.
*
* @param seen
* the opcode of the currently parsed instruction
*/
@Override
public void sawOpcode(int seen) {
try {
stack.precomputation(this);
if (seen == INVOKEINTERFACE) {
KeyType type = isKeyAccessMethod(seen);
if (type != null) {
int numParms = SignatureUtils.getNumParameters(getSigConstantOperand());
if (stack.getStackDepth() >= numParms) {
OpcodeStack.Item item = stack.getStackItem(numParms - 1);
String parmName = (String) item.getConstant();
if (parmName != null) {
String upperParmName = parmName.toUpperCase(Locale.getDefault());
Map>> typeMap = parmInfo.get(KeyType.PARAMETER);
Map> parmCaseInfo = typeMap.get(upperParmName);
if (parmCaseInfo == null) {
parmCaseInfo = new HashMap<>();
typeMap.put(upperParmName, parmCaseInfo);
}
List annotations = parmCaseInfo.get(parmName);
if (annotations == null) {
annotations = new ArrayList<>();
parmCaseInfo.put(parmName, annotations);
}
annotations.add(new SourceInfo(getClassName(), getMethodName(), getMethodSig(), getMethod().isStatic(),
SourceLineAnnotation.fromVisitedInstruction(getClassContext(), this, getPC())));
}
}
}
}
} finally {
stack.sawOpcode(this, seen);
}
}
/**
* implements the visitor to look for the collected parm names, and look for duplicates that are different in casing only.
*/
@Override
public void report() {
for (Map.Entry>>> entry : parmInfo.entrySet()) {
KeyType type = entry.getKey();
Map>> typeMap = entry.getValue();
for (Map> parmCaseInfo : typeMap.values()) {
if (parmCaseInfo.size() > 1) {
BugInstance bi = new BugInstance(this, type.getDescription(), NORMAL_PRIORITY);
for (Map.Entry> sourceInfos : parmCaseInfo.entrySet()) {
for (SourceInfo sourceInfo : sourceInfos.getValue()) {
bi.addClass(sourceInfo.clsName);
bi.addMethod(sourceInfo.clsName, sourceInfo.methodName, sourceInfo.signature, sourceInfo.isStatic);
bi.addSourceLine(sourceInfo.srcLine);
bi.addString(sourceInfos.getKey());
}
}
bugReporter.reportBug(bi);
}
}
}
parmInfo.clear();
}
/**
* looks to see if this method is a getAttribute/setAttribute on Session or getParameter on HttpServletRequest
*
* @param seen
* the currently parsed opcode
* @return if it is one of these special methods
*/
@Nullable
private KeyType isKeyAccessMethod(int seen) {
if (seen == INVOKEINTERFACE) {
String clsName = getClassConstantOperand();
if (HTTP_SESSION.equals(clsName)) {
String methodName = getNameConstantOperand();
if (GET_ATTRIBUTE.equals(methodName)) {
String signature = getSigConstantOperand();
return (GET_ATTRIBUTE_SIG.equals(signature)) ? KeyType.ATTRIBUTE : null;
} else if (SET_ATTRIBUTE.equals(methodName)) {
String signature = getSigConstantOperand();
return (SET_ATTRIBUTE_SIG.equals(signature)) ? KeyType.ATTRIBUTE : null;
}
} else if (HTTP_SERVLET_REQUEST.equals(clsName)) {
String methodName = getNameConstantOperand();
if (GET_PARAMETER.equals(methodName)) {
String signature = getSigConstantOperand();
return (GET_PARAMETER_SIG.equals(signature)) ? KeyType.PARAMETER : null;
}
}
}
return null;
}
/**
* a holder for location information of a getParameter call
*/
static class SourceInfo {
String clsName;
String methodName;
String signature;
boolean isStatic;
SourceLineAnnotation srcLine;
SourceInfo(String cls, String method, String sig, boolean mStatic, SourceLineAnnotation annotation) {
clsName = cls;
methodName = method;
signature = sig;
isStatic = mStatic;
srcLine = annotation;
}
@Override
public String toString() {
return ToString.build(this);
}
}
}