src.main.java.com.mebigfatguy.fbcontrib.detect.SillynessPotPourri Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sb-contrib Show documentation
Show all versions of sb-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.
The newest version!
/*
* fb-contrib - Auxiliary detectors for Java programs
* Copyright (C) 2005-2019 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.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.apache.bcel.Const;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantDouble;
import org.apache.bcel.classfile.ConstantMethodref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ConstantString;
import org.apache.bcel.classfile.ConstantValue;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.LocalVariable;
import org.apache.bcel.classfile.LocalVariableTable;
import org.apache.bcel.classfile.Method;
import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.CodeByteUtils;
import com.mebigfatguy.fbcontrib.utils.OpcodeUtils;
import com.mebigfatguy.fbcontrib.utils.QMethod;
import com.mebigfatguy.fbcontrib.utils.RegisterUtils;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.TernaryPatcher;
import com.mebigfatguy.fbcontrib.utils.ToString;
import com.mebigfatguy.fbcontrib.utils.UnmodifiableSet;
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.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
import edu.umd.cs.findbugs.visitclass.LVTHelper;
/**
* looks for silly bugs that are simple but do not fit into one large pattern.
*/
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "CLI_CONSTANT_LIST_INDEX", justification = "lastPCs is an int[] of size 4 for efficiency reasons")
@CustomUserValue
public class SillynessPotPourri extends BytecodeScanningDetector {
private static final Set collectionInterfaces = UnmodifiableSet.create(Values.SLASHED_JAVA_UTIL_COLLECTION,
Values.SLASHED_JAVA_UTIL_LIST, Values.SLASHED_JAVA_UTIL_SET, "java/util/SortedSet",
Values.SLASHED_JAVA_UTIL_MAP, "java/util/SortedMap");
private static final Set oddMissingEqualsClasses = UnmodifiableSet.create("java.lang.StringBuffer",
"java.lang.StringBuilder");
private static final String LITERAL = "literal";
private static final Pattern APPEND_PATTERN = Pattern.compile("([0-9]+):(.*)");
private static JavaClass calendarClass;
static {
try {
calendarClass = Repository.lookupClass("java/util/Calendar");
} catch (ClassNotFoundException cnfe) {
calendarClass = null;
}
}
private static Map methodsThatAreSillyOnStringLiterals = new HashMap<>();
static {
String localeToString = new SignatureBuilder().withParamTypes("java/util/Locale")
.withReturnType(Values.SLASHED_JAVA_LANG_STRING).toString();
methodsThatAreSillyOnStringLiterals.put(new QMethod("toLowerCase", SignatureBuilder.SIG_VOID_TO_STRING),
Values.ZERO);
methodsThatAreSillyOnStringLiterals.put(new QMethod("toUpperCase", SignatureBuilder.SIG_VOID_TO_STRING),
Values.ZERO);
methodsThatAreSillyOnStringLiterals.put(new QMethod("toLowerCase", localeToString), Values.ONE);
methodsThatAreSillyOnStringLiterals.put(new QMethod("toUpperCase", localeToString), Values.ONE);
methodsThatAreSillyOnStringLiterals.put(new QMethod("trim", SignatureBuilder.SIG_VOID_TO_STRING), Values.ZERO);
methodsThatAreSillyOnStringLiterals.put(new QMethod("isEmpty", SignatureBuilder.SIG_VOID_TO_BOOLEAN),
Values.ZERO);
}
private final BugReporter bugReporter;
private final Set toStringClasses;
private OpcodeStack stack;
private int lastPCs[];
private int lastOpcode;
private int lastReg;
private boolean lastIfEqWasBoolean;
private boolean lastLoadWasString;
/** branch targets, to a set of branch instructions */
private Map branchTargets;
private Set staticConstants;
private Map trimLocations;
private boolean isInterface;
/**
* constructs a SPP detector given the reporter to report bugs on
*
* @param bugReporter the sync of bug reports
*/
public SillynessPotPourri(BugReporter bugReporter) {
this.bugReporter = bugReporter;
toStringClasses = new HashSet<>();
}
@Override
public void visitField(Field field) {
if (!isInterface && "serialVersionUID".equals(field.getName()) && field.isStatic() && !field.isPrivate()) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_SERIALVER_SHOULD_BE_PRIVATE.name(), LOW_PRIORITY)
.addClass(this).addField(this));
}
}
@Override
public void visitClassContext(ClassContext classContext) {
try {
stack = new OpcodeStack();
lastPCs = new int[4];
branchTargets = new HashMap<>();
trimLocations = new HashMap<>();
isInterface = classContext.getJavaClass().isInterface();
super.visitClassContext(classContext);
} finally {
stack = null;
lastPCs = null;
branchTargets = null;
trimLocations = null;
staticConstants = null;
}
}
/**
* implements the visitor to reset the opcode stack
*
* @param obj the context object for the currently parsed Code
*/
@Override
public void visitCode(Code obj) {
stack.resetForMethodEntry(this);
lastOpcode = -1;
lastReg = -1;
lastIfEqWasBoolean = false;
lastLoadWasString = false;
Arrays.fill(lastPCs, -1);
branchTargets.clear();
trimLocations.clear();
super.visitCode(obj);
}
/**
* implements the visitor to look for various silly bugs
*
* @param seen the opcode of the currently parsed instruction
*/
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH", justification = "This fall-through is deliberate and documented")
@Override
public void sawOpcode(int seen) {
int reg = -1;
SPPUserValue userValue = null;
try {
stack.precomputation(this);
checkTrimLocations();
if (isBranchByteCode(seen)) {
Integer branchTarget = Integer.valueOf(getBranchTarget());
BitSet branchInsSet = branchTargets.get(branchTarget);
if (branchInsSet == null) {
branchInsSet = new BitSet();
branchTargets.put(branchTarget, branchInsSet);
}
branchInsSet.set(getPC());
}
// not an else if, because some of the opcodes in the previous
// branch also matter here.
if (seen == Const.IFEQ || seen == Const.IFLE || seen == Const.IFNE) {
checkForEmptyStringAndNullChecks(seen);
}
// see above, several opcodes hit multiple branches.
if (seen == Const.IFEQ || seen == Const.IFNE || seen == Const.IFGT) {
checkSizeEquals0();
}
if (seen == Const.IFEQ) {
checkNullAndInstanceOf();
}
switch (seen) {
case Const.IFNE:
checkNotEqualsStringBuilderLength();
break;
case Const.IFEQ:
checkEqualsStringBufferLength();
break;
case Const.IRETURN: {
if (lastIfEqWasBoolean) {
checkForUselessTernaryReturn();
}
}
// $FALL-THROUGH$
case Const.LRETURN:
case Const.DRETURN:
case Const.FRETURN:
case Const.ARETURN:
trimLocations.clear();
break;
case Const.LDC2_W:
checkApproximationsOfMathConstants();
break;
case Const.DCMPL:
checkCompareToNaNDouble();
break;
case Const.FCMPL:
checkCompareToNaNFloat();
break;
case Const.ICONST_0:
case Const.ICONST_1:
case Const.ICONST_2:
case Const.ICONST_3:
userValue = sawIntConst();
break;
case Const.CALOAD:
checkImproperToCharArrayUse();
break;
case Const.INVOKESTATIC:
userValue = sawInvokeStatic();
break;
case Const.INVOKEVIRTUAL:
userValue = sawInvokeVirtual();
break;
case Const.INVOKESPECIAL:
sawInvokeSpecial();
break;
case Const.INVOKEINTERFACE:
userValue = sawInvokeInterface();
break;
case Const.IF_ICMPEQ:
case Const.IF_ICMPGE:
case Const.IF_ICMPGT:
case Const.IF_ICMPLE:
case Const.IF_ICMPLT:
case Const.IF_ICMPNE:
if (stack.getStackDepth() >= 2) {
OpcodeStack.Item first = stack.getStackItem(1);
OpcodeStack.Item second = stack.getStackItem(0);
SPPUserValue uv = (SPPUserValue) first.getUserValue();
Integer c = null;
if (uv != null && uv.getMethod() == SPPMethod.COMPARETO) {
c = (Integer) second.getConstant();
} else {
uv = (SPPUserValue) second.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.COMPARETO) {
c = (Integer) first.getConstant();
}
}
if (uv != null && uv.getMethod() == SPPMethod.COMPARETO && (c == null || c.intValue() != 0)) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_USE_ZERO_WITH_COMPARATOR.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
break;
default:
if (OpcodeUtils.isALoad(seen)) {
sawLoad(seen);
} else if (OpcodeUtils.isAStore(seen)) {
reg = RegisterUtils.getAStoreReg(this, seen);
checkTrimDupStore();
checkStutterdAssignment(seen, reg);
checkImmutableUsageOfStringBuilder(reg);
}
}
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
} finally {
TernaryPatcher.pre(stack, seen);
stack.sawOpcode(this, seen);
TernaryPatcher.post(stack, seen);
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
if (userValue != null) {
item.setUserValue(userValue);
} else {
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.ITERATOR && seen == Const.GETFIELD
|| seen == Const.ALOAD || seen >= Const.ALOAD_0 && seen <= Const.ALOAD_3) {
item.setUserValue(null);
}
}
}
lastOpcode = seen;
lastReg = reg;
System.arraycopy(lastPCs, 1, lastPCs, 0, 3);
lastPCs[3] = getPC();
}
}
private void checkImproperToCharArrayUse() {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.ICONST) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USE_CHARAT.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
@Nullable
private SPPUserValue sawIntConst() {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.TOCHARARRAY) {
return new SPPUserValue(SPPMethod.ICONST);
}
}
return null;
}
private void sawLoad(int seen) {
lastLoadWasString = false;
LocalVariableTable lvt = getMethod().getLocalVariableTable();
if (lvt != null) {
LocalVariable lv = LVTHelper.getLocalVariableAtPC(lvt, RegisterUtils.getALoadReg(this, seen), getPC());
if (lv != null) {
lastLoadWasString = Values.SIG_JAVA_LANG_STRING.equals(lv.getSignature());
}
}
}
/**
* determines whether this operation is storing the result of a trim() call,
* where the trimmed string was duplicated on the stack. If it was, it clears
* any trim uservalue that was left behind in the dupped stack object
*/
private void checkTrimDupStore() {
if (stack.getStackDepth() >= 2 && getPrevOpcode(1) == Const.DUP) {
OpcodeStack.Item item = stack.getStackItem(0);
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv == null || uv.getMethod() != SPPMethod.TRIM) {
return;
}
item = stack.getStackItem(1);
uv = (SPPUserValue) item.getUserValue();
if (uv == null || uv.getMethod() != SPPMethod.TRIM) {
return;
}
item.setUserValue(null);
}
}
private void checkStutterdAssignment(int seen, int reg) {
if (seen == lastOpcode && reg == lastReg) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_STUTTERED_ASSIGNMENT.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
private void checkImmutableUsageOfStringBuilder(int reg) {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
SPPUserValue userValue = (SPPUserValue) item.getUserValue();
if (userValue != null) {
if (userValue.getMethod() == SPPMethod.TRIM) {
item.setUserValue(null);
} else if (userValue.getMethod() == SPPMethod.APPEND) {
Matcher m = APPEND_PATTERN.matcher(userValue.getDetails());
if (m.matches()) {
int appendReg = Integer.parseInt(m.group(1));
if (reg == appendReg) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_STRINGBUILDER_IS_MUTABLE.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
}
}
private void checkCompareToNaNFloat() {
if (stack.getStackDepth() > 1) {
OpcodeStack.Item item = stack.getStackItem(0);
Float f1 = (Float) item.getConstant();
item = stack.getStackItem(1);
Float f2 = (Float) item.getConstant();
if (f1 != null && f1.isNaN() || f2 != null && f2.isNaN()) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USE_ISNAN.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this).addString("float").addString("Float"));
}
}
}
private void checkCompareToNaNDouble() {
if (stack.getStackDepth() > 1) {
OpcodeStack.Item item = stack.getStackItem(0);
Double d1 = (Double) item.getConstant();
item = stack.getStackItem(1);
Double d2 = (Double) item.getConstant();
if (d1 != null && d1.isNaN() || d2 != null && d2.isNaN()) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USE_ISNAN.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this).addString("double").addString("Double"));
}
}
}
private void checkApproximationsOfMathConstants() {
Object con = getConstantRefOperand();
if (con instanceof ConstantDouble) {
double d = ((ConstantDouble) con).getBytes();
double piDelta = Math.abs(d - Math.PI);
double eDelta = Math.abs(d - Math.E);
if (piDelta > 0.0 && piDelta < 0.002 || eDelta > 0.0 && eDelta < 0.002) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USE_MATH_CONSTANT.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
private void checkForUselessTernaryReturn() {
byte[] bytes = getCode().getCode();
if (lastPCs[0] != -1 && (0x00FF & bytes[lastPCs[3]]) == Const.ICONST_0
&& (0x00FF & bytes[lastPCs[2]]) == Const.GOTO && (0x00FF & bytes[lastPCs[1]]) == Const.ICONST_1
&& (0x00FF & bytes[lastPCs[0]]) == Const.IFEQ
&& getMethod().getSignature().endsWith(Values.SIG_PRIMITIVE_BOOLEAN)) {
boolean bug = true;
BitSet branchInsSet = branchTargets.get(Integer.valueOf(lastPCs[1]));
if (branchInsSet != null) {
bug = false;
}
branchInsSet = branchTargets.get(Integer.valueOf(lastPCs[3]));
if (branchInsSet != null && branchInsSet.cardinality() > 1) {
bug = false;
}
if (bug) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USELESS_TERNARY.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
private void checkEqualsStringBufferLength() {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item itm = stack.getStackItem(0);
lastIfEqWasBoolean = Values.SIG_PRIMITIVE_BOOLEAN.equals(itm.getSignature());
}
byte[] bytes = getCode().getCode();
if (lastPCs[1] != -1 && CodeByteUtils.getbyte(bytes, lastPCs[3]) == Const.INVOKEVIRTUAL) {
int loadIns = CodeByteUtils.getbyte(bytes, lastPCs[2]);
if ((loadIns == Const.LDC || loadIns == Const.LDC_W)
&& CodeByteUtils.getbyte(bytes, lastPCs[1]) == Const.INVOKEVIRTUAL) {
ConstantPool pool = getConstantPool();
int toStringIndex = CodeByteUtils.getshort(bytes, lastPCs[1] + 1);
Constant cmr = pool.getConstant(toStringIndex);
if (cmr instanceof ConstantMethodref) {
ConstantMethodref toStringMR = (ConstantMethodref) cmr;
String toStringCls = toStringMR.getClass(pool);
if (toStringCls.startsWith("java.lang.StringBu")) {
int consIndex = CodeByteUtils.getbyte(bytes, lastPCs[2] + 1);
Constant c = pool.getConstant(consIndex);
if (c instanceof ConstantString && ((ConstantString) c).getBytes(pool).isEmpty()) {
int nandtIndex = toStringMR.getNameAndTypeIndex();
ConstantNameAndType cnt = (ConstantNameAndType) pool.getConstant(nandtIndex);
if (Values.TOSTRING.equals(cnt.getName(pool))) {
int lengthIndex = CodeByteUtils.getshort(bytes, lastPCs[3] + 1);
ConstantMethodref lengthMR = (ConstantMethodref) pool.getConstant(lengthIndex);
nandtIndex = lengthMR.getNameAndTypeIndex();
cnt = (ConstantNameAndType) pool.getConstant(nandtIndex);
if ("equals".equals(cnt.getName(pool))) {
bugReporter.reportBug(new BugInstance(this,
BugType.SPP_USE_STRINGBUILDER_LENGTH.name(), NORMAL_PRIORITY).addClass(this)
.addMethod(this).addSourceLine(this));
}
}
}
}
}
}
}
}
private void checkNotEqualsStringBuilderLength() {
byte[] bytes = getCode().getCode();
if (lastPCs[2] != -1 && CodeByteUtils.getbyte(bytes, lastPCs[3]) == Const.INVOKEVIRTUAL
&& CodeByteUtils.getbyte(bytes, lastPCs[2]) == Const.INVOKEVIRTUAL) {
ConstantPool pool = getConstantPool();
int toStringIndex = CodeByteUtils.getshort(bytes, lastPCs[2] + 1);
ConstantMethodref toStringMR = (ConstantMethodref) pool.getConstant(toStringIndex);
String toStringCls = toStringMR.getClass(pool);
if (toStringCls.startsWith("java.lang.StringBu")) {
int nandtIndex = toStringMR.getNameAndTypeIndex();
ConstantNameAndType cnt = (ConstantNameAndType) pool.getConstant(nandtIndex);
if (Values.TOSTRING.equals(cnt.getName(pool))) {
int lengthIndex = CodeByteUtils.getshort(bytes, lastPCs[3] + 1);
ConstantMethodref lengthMR = (ConstantMethodref) pool.getConstant(lengthIndex);
nandtIndex = lengthMR.getNameAndTypeIndex();
cnt = (ConstantNameAndType) pool.getConstant(nandtIndex);
if ("length".equals(cnt.getName(pool))) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_USE_STRINGBUILDER_LENGTH.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
}
private void checkNullAndInstanceOf() {
byte[] bytes = getCode().getCode();
if (lastPCs[0] != -1 && CodeByteUtils.getbyte(bytes, lastPCs[1]) == Const.IFNULL
&& CodeByteUtils.getbyte(bytes, lastPCs[3]) == Const.INSTANCEOF) {
int ins0 = CodeByteUtils.getbyte(bytes, lastPCs[0]);
if (OpcodeUtils.isALoad(ins0)) {
int ins2 = CodeByteUtils.getbyte(bytes, lastPCs[2]);
if (ins0 == ins2 && (ins0 != Const.ALOAD || CodeByteUtils.getbyte(bytes,
lastPCs[0] + 1) == CodeByteUtils.getbyte(bytes, lastPCs[2] + 1))) {
int ifNullTarget = lastPCs[1] + CodeByteUtils.getshort(bytes, lastPCs[1] + 1);
if (ifNullTarget == getBranchTarget()) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_NULL_BEFORE_INSTANCEOF.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
}
private void checkSizeEquals0() {
if (stack.getStackDepth() == 1) {
OpcodeStack.Item item = stack.getStackItem(0);
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.SIZE) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USE_ISEMPTY.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
private void checkForEmptyStringAndNullChecks(int seen) {
if (lastLoadWasString && lastPCs[0] != -1) {
byte[] bytes = getCode().getCode();
int loadIns = CodeByteUtils.getbyte(bytes, lastPCs[2]);
if ((loadIns >= Const.ALOAD_0 && loadIns <= Const.ALOAD_3 || loadIns == Const.ALOAD)
&& CodeByteUtils.getbyte(bytes, lastPCs[3]) == Const.INVOKEVIRTUAL
&& CodeByteUtils.getbyte(bytes, lastPCs[2]) == loadIns
&& CodeByteUtils.getbyte(bytes, lastPCs[1]) == Const.IFNULL
&& CodeByteUtils.getbyte(bytes, lastPCs[0]) == loadIns && (loadIns != Const.ALOAD || CodeByteUtils
.getbyte(bytes, lastPCs[2] + 1) == CodeByteUtils.getbyte(bytes, lastPCs[0] + 1))) {
int brOffset = loadIns == Const.ALOAD ? 11 : 10;
if (seen == Const.IFNE ? CodeByteUtils.getshort(bytes, lastPCs[1] + 1) > brOffset
: CodeByteUtils.getshort(bytes, lastPCs[1] + 1) == brOffset) {
int nextOp = CodeByteUtils.getbyte(bytes, getNextPC());
if (nextOp != Const.GOTO && nextOp != Const.GOTO_W) {
ConstantPool pool = getConstantPool();
int mpoolIndex = CodeByteUtils.getshort(bytes, lastPCs[3] + 1);
ConstantMethodref cmr = (ConstantMethodref) pool.getConstant(mpoolIndex);
int nandtIndex = cmr.getNameAndTypeIndex();
ConstantNameAndType cnt = (ConstantNameAndType) pool.getConstant(nandtIndex);
if ("length".equals(cnt.getName(pool))) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_SUSPECT_STRING_TEST.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
}
}
private static boolean isBranchByteCode(int seen) {
return seen >= Const.IFEQ && seen <= Const.GOTO || seen == Const.IFNULL || seen == Const.IFNONNULL
|| seen == Const.GOTO_W;
}
private SPPUserValue sawInvokeStatic() {
SPPUserValue userValue = null;
String className = getClassConstantOperand();
String methodName = getNameConstantOperand();
if (Values.SLASHED_JAVA_LANG_SYSTEM.equals(className)) {
if ("getProperties".equals(methodName)) {
userValue = new SPPUserValue(SPPMethod.GETPROPERTIES);
} else if ("arraycopy".equals(methodName) && stack.getStackDepth() >= 5) {
checkForArrayParameter(stack.getStackItem(2));
checkForArrayParameter(stack.getStackItem(4));
}
} else if ("java/lang/reflect/Array".equals(className)) {
int offset = -1;
if ("getLength".equals(methodName)) {
offset = 0;
} else if (methodName.startsWith("get")) {
offset = 1;
} else if (methodName.startsWith("set")) {
offset = 2;
}
if (offset >= 0 && stack.getStackDepth() > offset) {
checkForArrayParameter(stack.getStackItem(offset));
}
} else if (Values.SLASHED_JAVA_LANG_STRING.equals(className) && "format".equals(methodName)
&& stack.getStackDepth() >= 2) {
OpcodeStack.Item item = stack.getStackItem(1);
String format = (String) item.getConstant();
if (format != null && !format.contains("%")) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_STATIC_FORMAT_STRING.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
} else if ("org/apache/commons/lang3/builder/ToStringBuilder".equals(className)
|| "org/apache/commons/lang/builder/ToStringBuilder".equals(className)) {
if ("reflectionToString".equals(methodName)
&& SignatureBuilder.SIG_OBJECT_TO_STRING.equals(getSigConstantOperand())) {
if (stack.getStackDepth() >= 1) {
OpcodeStack.Item itm = stack.getStackItem(0);
String toStringSig = itm.getSignature();
if (toStringSig != null && toStringSig.contains("/ToStringStyle")) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_WRONG_COMMONS_TO_STRING_OBJECT.name(),
NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
return userValue;
}
private void checkForArrayParameter(OpcodeStack.Item item) {
String sig = item.getSignature();
if (!sig.startsWith(Values.SIG_ARRAY_PREFIX) && !Values.SIG_JAVA_LANG_OBJECT.equals(sig)) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_NON_ARRAY_PARM.name(), HIGH_PRIORITY).addClass(this)
.addMethod(this).addSourceLine(this));
}
}
@Nullable
private SPPUserValue sawInvokeVirtual() throws ClassNotFoundException {
checkThisParm();
String className = getClassConstantOperand();
String methodName = getNameConstantOperand();
if ("java/util/BitSet".equals(className)) {
bitSetSilliness(methodName);
} else if (SignatureUtils.isPlainStringConvertableClass(className)) {
return stringBufferSilliness(methodName);
} else if (Values.SLASHED_JAVA_LANG_STRING.equals(className)) {
return stringSilliness(methodName, getSigConstantOperand());
} else if ("equals".equals(methodName)
&& SignatureBuilder.SIG_OBJECT_TO_BOOLEAN.equals(getSigConstantOperand())) {
equalsSilliness(className);
} else if ("java/lang/Boolean".equals(className) && "booleanValue".equals(methodName)) {
booleanSilliness();
} else if (("java/util/GregorianCalendar".equals(className) || "java/util/Calendar".equals(className))
&& ("after".equals(methodName) || "before".equals(methodName))) {
calendarBeforeAfterSilliness();
} else if ("java/util/Properties".equals(className)) {
propertiesSilliness(methodName);
} else if (Values.TOSTRING.equals(methodName) && Values.SLASHED_JAVA_LANG_OBJECT.equals(className)) {
defaultToStringSilliness();
} else if ("compareTo".equals(methodName)) {
String sig = getSigConstantOperand();
if ("I".equals(SignatureUtils.getReturnSignature(sig))) {
List parms = SignatureUtils.getParameterSignatures(sig);
if (parms.size() == 1 && className.equals(SignatureUtils.trimSignature(parms.get(0)))) {
return new SPPUserValue(SPPMethod.COMPARETO);
}
}
}
return null;
}
private void bitSetSilliness(String methodName) {
if (("clear".equals(methodName) || "flip".equals(methodName) || "get".equals(methodName)
|| "set".equals(methodName)) && stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
Object o = item.getConstant();
if (o instanceof Integer && ((Integer) o).intValue() < 0) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_NEGATIVE_BITSET_ITEM.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
@Nullable
private SPPUserValue stringBufferSilliness(String methodName) {
if ("append".equals(methodName) && stack.getStackDepth() > 1) {
OpcodeStack.Item valItem = stack.getStackItem(0);
OpcodeStack.Item sbItem = stack.getStackItem(1);
Object constant = valItem.getConstant();
boolean argIsLiteralString = constant instanceof String && ((String) constant).length() > 0;
argIsLiteralString = argIsLiteralString && !looksLikeStaticFieldValue((String) constant);
if (argIsLiteralString) {
SPPUserValue uv = (SPPUserValue) sbItem.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.APPEND) {
Matcher m = APPEND_PATTERN.matcher(uv.getDetails());
if (m.matches() && LITERAL.equals(m.group(2))) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_DOUBLE_APPENDED_LITERALS.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
argIsLiteralString = false;
}
}
}
String literal = argIsLiteralString ? LITERAL : "";
SPPUserValue userValue = null;
int registerNumber = sbItem.getRegisterNumber();
if (registerNumber > -1) {
userValue = new SPPUserValue(SPPMethod.APPEND, registerNumber + ':' + literal);
} else {
userValue = (SPPUserValue) sbItem.getUserValue();
if (userValue != null && userValue.getMethod() == SPPMethod.APPEND) {
Matcher m = APPEND_PATTERN.matcher(userValue.getDetails());
if (m.matches()) {
userValue = new SPPUserValue(SPPMethod.APPEND, m.group(1) + ':' + literal);
}
}
}
return userValue;
}
return null;
}
private SPPUserValue stringSilliness(String methodName, String signature) {
Integer stackOffset = methodsThatAreSillyOnStringLiterals.get(new QMethod(methodName, signature));
int offset;
if (stackOffset != null && stack.getStackDepth() > (offset = stackOffset.intValue())) {
OpcodeStack.Item itm = stack.getStackItem(offset);
Object constant = itm.getConstant();
if (constant != null && constant.getClass().equals(String.class) && itm.getXField() == null) {
int priority = NORMAL_PRIORITY;
if (SignatureUtils.getNumParameters(getSigConstantOperand()) > 0) {
// if an argument is passed in, it may be
// locale-specific
priority = LOW_PRIORITY;
}
bugReporter.reportBug(new BugInstance(this, BugType.SPP_CONVERSION_OF_STRING_LITERAL.name(), priority)
.addClass(this).addMethod(this).addSourceLine(this).addCalledMethod(this));
}
}
// not an elseif because the below cases might be in the set
// methodsThatAreSillyOnStringLiterals
SPPUserValue userValue = null;
if ("intern".equals(methodName)) {
String owningMethod = getMethod().getName();
if (!Values.STATIC_INITIALIZER.equals(owningMethod) && stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
if (item.getConstant() != null) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_INTERN_ON_CONSTANT.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
} else if ("toCharArray".equals(methodName)) {
userValue = new SPPUserValue(SPPMethod.TOCHARARRAY);
} else if ("toLowerCase".equals(methodName) || "toUpperCase".equals(methodName)) {
userValue = new SPPUserValue(SPPMethod.IGNORECASE);
} else if ("equalsIgnoreCase".equals(methodName) || "compareToIgnoreCase".equals(methodName)) {
if (stack.getStackDepth() > 1) {
OpcodeStack.Item item = stack.getStackItem(1);
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.IGNORECASE) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USELESS_CASING.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
item = stack.getStackItem(0);
String parm = (String) item.getConstant();
if ("".equals(parm)) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_EMPTY_CASING.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
} else if ("trim".equals(methodName)) {
userValue = getTrimUserValue();
} else if ("length".equals(methodName)) {
if (stack.getStackDepth() > 0) {
checkForTrim(stack.getStackItem(0));
}
} else if ("equals".equals(methodName)) {
if (stack.getStackDepth() > 1) {
checkForTrim(stack.getStackItem(1));
}
} else if (Values.TOSTRING.equals(methodName)) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_TOSTRING_ON_STRING.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
return userValue;
}
private void checkForTrim(OpcodeStack.Item item) {
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.TRIM) {
if (uv.getDetails() == null) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_TEMPORARY_TRIM.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
} else {
trimLocations.put(uv, Integer.valueOf(getPC()));
}
}
}
private void equalsSilliness(String className) {
try {
JavaClass cls = Repository.lookupClass(className);
if (!cls.isEnum()) {
if (stack.getStackDepth() >= 2) {
OpcodeStack.Item item = stack.getStackItem(1);
cls = item.getJavaClass();
if (cls != null) {
String clsName = cls.getClassName();
if (oddMissingEqualsClasses.contains(clsName)) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_EQUALS_ON_STRING_BUILDER.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
}
}
private void booleanSilliness() {
if (lastPCs[0] != -1) {
int range1Size = lastPCs[2] - lastPCs[0];
if (range1Size == getNextPC() - lastPCs[3]) {
byte[] bytes = getCode().getCode();
int ifeq = 0x000000FF & bytes[lastPCs[2]];
if (ifeq == Const.IFEQ) {
int start1 = lastPCs[0];
int start2 = lastPCs[3];
boolean found = true;
for (int i = 0; i < range1Size; i++) {
if (bytes[start1 + i] != bytes[start2 + i]) {
found = false;
break;
}
}
if (found) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_INVALID_BOOLEAN_NULL_CHECK.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
}
private void calendarBeforeAfterSilliness() {
if (stack.getStackDepth() > 1) {
OpcodeStack.Item item = stack.getStackItem(0);
String itemSig = item.getSignature();
// Rule out java.lang.Object as mergeJumps can throw away type info
// (BUG)
if (!Values.SIG_JAVA_LANG_OBJECT.equals(itemSig) && !"Ljava/util/Calendar;".equals(itemSig)
&& !"Ljava/util/GregorianCalendar;".equals(itemSig)) {
try {
JavaClass cls = Repository.lookupClass(SignatureUtils.stripSignature(itemSig));
if (!cls.instanceOf(calendarClass)) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_INVALID_CALENDAR_COMPARE.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
}
}
}
}
private void defaultToStringSilliness() throws ClassNotFoundException {
if (stack.getStackDepth() >= 1) {
OpcodeStack.Item item = stack.getStackItem(0);
JavaClass toStringClass = item.getJavaClass();
if (toStringClass != null) {
String toStringClassName = toStringClass.getClassName();
if (!toStringClass.isInterface() && !toStringClass.isAbstract()
&& !Values.DOTTED_JAVA_LANG_OBJECT.equals(toStringClassName)
&& !Values.DOTTED_JAVA_LANG_STRING.equals(toStringClassName)
&& toStringClasses.add(toStringClassName)) {
try {
JavaClass cls = Repository.lookupClass(toStringClassName);
if (!hasToString(cls)) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_NON_USEFUL_TOSTRING.name(),
toStringClass.isFinal() ? NORMAL_PRIORITY : LOW_PRIORITY).addClass(this)
.addMethod(this).addSourceLine(this));
}
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
}
}
}
}
}
private void propertiesSilliness(String methodName) {
if (("get".equals(methodName) || "getProperty".equals(methodName)) && stack.getStackDepth() > 1) {
OpcodeStack.Item item = stack.getStackItem(1);
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.GETPROPERTIES) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USE_GETPROPERTY.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
private SPPUserValue sawInvokeInterface() {
checkThisParm();
SPPUserValue userValue = null;
String className = getClassConstantOperand();
if (Values.SLASHED_JAVA_UTIL_LIST.equals(className)) {
String method = getNameConstantOperand();
if ("iterator".equals(method)) {
userValue = new SPPUserValue(SPPMethod.ITERATOR);
}
} else if ("java/util/Iterator".equals(className)) {
String method = getNameConstantOperand();
if ("next".equals(method) && stack.getStackDepth() >= 1) {
OpcodeStack.Item item = stack.getStackItem(0);
SPPUserValue uv = (SPPUserValue) item.getUserValue();
if (uv != null && uv.getMethod() == SPPMethod.ITERATOR) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_USE_GET0.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
if (collectionInterfaces.contains(className)) {
String method = getNameConstantOperand();
if ("size".equals(method)) {
if (!OpcodeUtils.isIStore(getNextOpcode())) {
userValue = new SPPUserValue(SPPMethod.SIZE);
}
}
}
return userValue;
}
/**
* checks to see if the object that the method is being called on is also passed
* as a parameter. To rule out simple mathish like calls like a.mul(a); only
* check two plus parameter calls.
*/
private void checkThisParm() {
int parmCnt = SignatureUtils.getNumParameters(getSigConstantOperand());
if (parmCnt <= 1 || stack.getStackDepth() < parmCnt + 1) {
return;
}
if (!SignatureUtils.similarPackages(getClassName(), getClassConstantOperand(), 2)) {
return;
}
OpcodeStack.Item thisObj = stack.getStackItem(parmCnt);
int thisReg = thisObj.getRegisterNumber();
XField xf = thisObj.getXField();
FieldDescriptor thisFD = null;
if (xf != null) {
thisFD = xf.getFieldDescriptor();
}
if (thisReg >= 0 || this.getXField() != null) {
for (int i = 0; i < parmCnt; i++) {
OpcodeStack.Item itm = stack.getStackItem(i);
int parmReg = itm.getRegisterNumber();
xf = itm.getXField();
FieldDescriptor parmFD = null;
if (xf != null) {
parmFD = xf.getFieldDescriptor();
}
if (thisReg >= 0 && thisReg == parmReg || thisFD != null && thisFD.equals(parmFD)) {
bugReporter
.reportBug(new BugInstance(this, BugType.SPP_PASSING_THIS_AS_PARM.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
private void sawInvokeSpecial() {
checkThisParm();
String className = getClassConstantOperand();
if (SignatureUtils.isPlainStringConvertableClass(className)) {
String methodName = getNameConstantOperand();
if (Values.CONSTRUCTOR.equals(methodName)) {
String signature = getSigConstantOperand();
if (SignatureBuilder.SIG_INT_TO_VOID.equals(signature)) {
if (lastOpcode == Const.BIPUSH && stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
Object o = item.getConstant();
if (o instanceof Integer) {
int parm = ((Integer) o).intValue();
if (parm > 32 && parm < 127 && parm != 64 && parm != 48 && parm % 5 != 0) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_NO_CHAR_SB_CTOR.name(), LOW_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
} else if (SignatureBuilder.SIG_STRING_TO_VOID.equals(signature) && stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
String con = (String) item.getConstant();
if ("".equals(con)) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_STRINGBUFFER_WITH_EMPTY_STRING.name(),
NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
}
}
}
} else if ("java/math/BigDecimal".equals(className) && stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
Object constant = item.getConstant();
if (constant instanceof Double) {
double v = ((Double) constant).doubleValue();
if (v != 0.0 && v != 1.0) {
bugReporter.reportBug(
new BugInstance(this, BugType.SPP_USE_BIGDECIMAL_STRING_CTOR.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
private boolean looksLikeStaticFieldValue(String constant) {
if (staticConstants == null) {
staticConstants = new HashSet<>();
Field[] fields = getClassContext().getJavaClass().getFields();
for (Field f : fields) {
if ((f.getAccessFlags() & (Const.ACC_FINAL | Const.ACC_STATIC)) == (Const.ACC_FINAL | Const.ACC_STATIC)
&& Values.SIG_JAVA_LANG_STRING.equals(f.getSignature())) {
ConstantValue cv = f.getConstantValue();
if (cv != null) {
int cvIndex = cv.getConstantValueIndex();
staticConstants.add(getConstantPool().getConstantString(cvIndex, Const.CONSTANT_String));
}
}
}
}
return staticConstants.contains(constant);
}
private boolean hasToString(JavaClass cls) throws ClassNotFoundException {
if (Values.DOTTED_JAVA_LANG_OBJECT.equals(cls.getClassName())) {
return false;
}
for (Method m : cls.getMethods()) {
if (Values.TOSTRING.equals(m.getName()) && SignatureBuilder.SIG_VOID_TO_STRING.equals(m.getSignature())) {
return true;
}
}
return hasToString(cls.getSuperClass());
}
@Nullable
private SPPUserValue getTrimUserValue() {
if (stack.getStackDepth() == 0) {
return null;
}
OpcodeStack.Item item = stack.getStackItem(0);
int reg = item.getRegisterNumber();
if (reg >= 0) {
return new SPPUserValue(SPPMethod.TRIM, String.valueOf(reg));
}
XField field = item.getXField();
if (field != null) {
return new SPPUserValue(SPPMethod.TRIM, field.getName());
}
XMethod method = item.getReturnValueOf();
if (method != null) {
return new SPPUserValue(SPPMethod.TRIM, method.getName());
}
return new SPPUserValue(SPPMethod.TRIM);
}
private void checkTrimLocations() {
if (trimLocations.isEmpty()) {
return;
}
SPPUserValue curV = getTrimUserValue();
if (curV == null) {
return;
}
Integer pc = trimLocations.remove(curV);
if (pc != null) {
bugReporter.reportBug(new BugInstance(this, BugType.SPP_TEMPORARY_TRIM.name(), NORMAL_PRIORITY)
.addClass(this).addMethod(this).addSourceLine(this, pc.intValue()));
}
}
enum SPPMethod {
APPEND, GETPROPERTIES, ICONST, IGNORECASE, ITERATOR, TOCHARARRAY, SIZE, TRIM, COMPARETO
}
static class SPPUserValue {
private SPPMethod method;
private String details;
public SPPUserValue(SPPMethod mthd) {
this(mthd, null);
}
public SPPUserValue(SPPMethod mthd, String detail) {
method = mthd;
details = detail;
}
public SPPMethod getMethod() {
return method;
}
public String getDetails() {
return details;
}
@Override
public int hashCode() {
return method.hashCode() ^ (details == null ? 0 : details.hashCode());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SPPUserValue)) {
return false;
}
SPPUserValue that = (SPPUserValue) o;
if (method != that.method) {
return false;
}
if (details == null) {
return that.details == null;
}
return details.equals(that.details);
}
@Override
public String toString() {
return ToString.build(this);
}
}
}