src.main.java.com.mebigfatguy.fbcontrib.detect.InefficientStringBuffering 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 javax.annotation.Nullable;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantString;
import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.OpcodeUtils;
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.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.XMethod;
/**
* looks for appending strings inside of calls to StringBuffer or StringBuilder append.
*/
@CustomUserValue
public class InefficientStringBuffering extends BytecodeScanningDetector {
private enum AppendType {
CLEAR, NESTED, TOSTRING
};
private BugReporter bugReporter;
private OpcodeStack stack;
private boolean sawLDCEmpty;
/**
* constructs a ISB detector given the reporter to report bugs on
*
* @param bugReporter
* the sync of bug reports
*/
public InefficientStringBuffering(final BugReporter bugReporter) {
this.bugReporter = bugReporter;
}
/**
* implements the visitor to create an clear 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;
}
}
/**
* implements the visitor to create and clear the stack
*
* @param obj
* the context object of the currently parsed code block
*/
@Override
public void visitCode(final Code obj) {
if (obj.getCode() != null) {
stack.resetForMethodEntry(this);
sawLDCEmpty = false;
super.visitCode(obj);
}
}
@Override
public void sawOpcode(final int seen) {
ISBUserValue userValue = null;
try {
stack.precomputation(this);
if (seen == INVOKESPECIAL) {
userValue = sawInvokeSpecial();
} else if (seen == INVOKEVIRTUAL) {
if (sawLDCEmpty) {
dealWithEmptyString();
}
userValue = sawInvokeVirtual();
} else if ((seen == GOTO) || (seen == GOTO_W)) {
int depth = stack.getStackDepth();
for (int i = 0; i < depth; i++) {
OpcodeStack.Item itm = stack.getStackItem(i);
itm.setUserValue(null);
}
} else if ((seen == LDC) || (seen == LDC_W)) {
Constant c = getConstantRefOperand();
if (c instanceof ConstantString) {
String s = ((ConstantString) c).getBytes(getConstantPool());
if (s.length() == 0) {
sawLDCEmpty = true;
}
}
} else if (OpcodeUtils.isALoad(seen)) {
userValue = new ISBUserValue(AppendType.CLEAR, true);
}
} finally {
handleOpcode(seen);
if ((userValue != null) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item itm = stack.getStackItem(0);
itm.setUserValue(userValue);
}
}
}
private void handleOpcode(final int seen) {
TernaryPatcher.pre(stack, seen);
stack.sawOpcode(this, seen);
TernaryPatcher.post(stack, seen);
}
private ISBUserValue sawInvokeVirtual() {
ISBUserValue userValue = null;
String calledClass = getClassConstantOperand();
if (SignatureUtils.isPlainStringConvertableClass(calledClass)) {
String methodName = getNameConstantOperand();
if ("append".equals(methodName)) {
OpcodeStack.Item itm = getStringBufferItemAt(1);
if (itm != null) {
userValue = (ISBUserValue) itm.getUserValue();
}
if (stack.getStackDepth() > 0) {
itm = stack.getStackItem(0);
ISBUserValue uv = (ISBUserValue) itm.getUserValue();
if (uv != null) {
switch (uv.getAppendType()) {
case NESTED:
bugReporter.reportBug(new BugInstance(this, BugType.ISB_INEFFICIENT_STRING_BUFFERING.name(),
Values.TOSTRING.equals(getMethodName()) ? LOW_PRIORITY : NORMAL_PRIORITY).addClass(this).addMethod(this)
.addSourceLine(this));
break;
case TOSTRING:
if (stack.getStackDepth() > 1) {
itm = stack.getStackItem(1);
if (itm != null) {
uv = (ISBUserValue) itm.getUserValue();
if ((uv != null) && uv.hasResolvedString()) {
bugReporter.reportBug(new BugInstance(this, BugType.ISB_TOSTRING_APPENDING.name(), NORMAL_PRIORITY).addClass(this)
.addMethod(this).addSourceLine(this));
}
}
}
break;
default:
break;
}
}
}
if (getSigConstantOperand().startsWith(SignatureBuilder.PARAM_STRING)) {
if (userValue == null) {
userValue = new ISBUserValue(AppendType.CLEAR, true);
} else {
userValue = new ISBUserValue(userValue.getAppendType(), true);
}
}
} else if (Values.TOSTRING.equals(methodName)) {
OpcodeStack.Item itm = getStringBufferItemAt(0);
if (itm != null) {
userValue = (ISBUserValue) itm.getUserValue();
}
}
} else if (Values.TOSTRING.equals(getNameConstantOperand()) && SignatureBuilder.SIG_VOID_TO_STRING.equals(getSigConstantOperand())
// calls to this.toString() are okay, some people like to be explicit
&& (stack.getStackDepth() > 0) && (stack.getStackItem(0).getRegisterNumber() != 0)) {
userValue = new ISBUserValue(AppendType.TOSTRING);
}
return userValue;
}
private void dealWithEmptyString() {
String calledClass = getClassConstantOperand();
if (SignatureUtils.isPlainStringConvertableClass(calledClass) && "append".equals(getNameConstantOperand())
&& getSigConstantOperand().startsWith(SignatureBuilder.PARAM_STRING) && (stack.getStackDepth() > 1)) {
OpcodeStack.Item sbItm = stack.getStackItem(1);
if ((sbItm != null) && (sbItm.getUserValue() == null)) {
OpcodeStack.Item itm = stack.getStackItem(0);
Object cons = itm.getConstant();
if ((cons instanceof String) && (itm.getRegisterNumber() < 0) && ((String) cons).isEmpty()) {
bugReporter.reportBug(
new BugInstance(this, BugType.ISB_EMPTY_STRING_APPENDING.name(), LOW_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
}
}
}
}
private ISBUserValue sawInvokeSpecial() {
ISBUserValue userValue = null;
String calledClass = getClassConstantOperand();
if (SignatureUtils.isPlainStringConvertableClass(calledClass) && Values.CONSTRUCTOR.equals(getNameConstantOperand())) {
String signature = getSigConstantOperand();
if (SignatureBuilder.SIG_VOID_TO_VOID.equals(signature)) {
OpcodeStack.Item itm = getStringBufferItemAt(2);
if (itm != null) {
userValue = new ISBUserValue(AppendType.NESTED);
}
} else if (SignatureBuilder.SIG_STRING_TO_VOID.equals(signature) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item itm = stack.getStackItem(0);
userValue = (ISBUserValue) itm.getUserValue();
if ((userValue != null) && (userValue.getAppendType() == AppendType.NESTED)) {
bugReporter.reportBug(new BugInstance(this, BugType.ISB_INEFFICIENT_STRING_BUFFERING.name(), NORMAL_PRIORITY).addClass(this).addMethod(this)
.addSourceLine(this));
}
if (userValue == null) {
XMethod m = itm.getReturnValueOf();
if ((m != null) && ("valueOf".equals(m.getName()) && Values.DOTTED_JAVA_LANG_STRING.equals(m.getClassName()))) {
userValue = new ISBUserValue(AppendType.CLEAR, false);
} else {
userValue = new ISBUserValue(AppendType.CLEAR, true);
}
}
}
}
return userValue;
}
@Nullable
private OpcodeStack.Item getStringBufferItemAt(int depth) {
if (stack.getStackDepth() > depth) {
OpcodeStack.Item itm = stack.getStackItem(depth);
String signature = itm.getSignature();
if ("Ljava/lang/StringBuffer;".equals(signature) || "Ljava/lang/StringBuilder;".equals(signature)) {
return itm;
}
}
return null;
}
static class ISBUserValue {
private AppendType appendType;
private boolean hasResolvedString;
public ISBUserValue(AppendType appType) {
this(appType, false);
}
public ISBUserValue(AppendType appType, boolean resolved) {
appendType = appType;
hasResolvedString = resolved;
}
public AppendType getAppendType() {
return appendType;
}
public boolean hasResolvedString() {
return hasResolvedString;
}
@Override
public int hashCode() {
return appendType.hashCode() ^ (hasResolvedString ? 1 : 0);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ISBUserValue)) {
return false;
}
ISBUserValue that = (ISBUserValue) obj;
return (appendType == that.appendType) && (hasResolvedString == that.hasResolvedString);
}
@Override
public String toString() {
return ToString.build(this);
}
}
}