src.main.java.com.mebigfatguy.fbcontrib.detect.StackedTryBlocks 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.
package com.mebigfatguy.fbcontrib.detect;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.apache.bcel.Constants;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.CodeException;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.OpcodeUtils;
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.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.XMethod;
/**
* looks for two or more try catch blocks that are consecutive and catch the same kind of exception, and throw the same exception always. These blocks can be
* coalesced into one.
*/
@CustomUserValue
public class StackedTryBlocks extends BytecodeScanningDetector {
private static JavaClass THROWABLE_CLASS;
static {
try {
THROWABLE_CLASS = Repository.lookupClass(Values.SLASHED_JAVA_LANG_THROWABLE);
} catch (ClassNotFoundException cnfe) {
THROWABLE_CLASS = null;
}
}
private final BugReporter bugReporter;
private List blocks;
private List inBlocks;
private BitSet transitionPoints;
private OpcodeStack stack;
public StackedTryBlocks(BugReporter bugReporter) {
this.bugReporter = bugReporter;
}
/**
* overrides the visitor to reset the opcode stack
*
* @param classContext
* the currently parsed class
*/
@Override
public void visitClassContext(ClassContext classContext) {
try {
if (THROWABLE_CLASS != null) {
stack = new OpcodeStack();
super.visitClassContext(classContext);
}
} finally {
stack = null;
}
}
/**
* overrides the visitor to look for 'idea' try catch blocks to find issues specifically, method needs two or more try catch blocks that only catch one
* exception type.
*
* @param obj
* the currently parsed code object
*/
@Override
public void visitCode(Code obj) {
try {
XMethod xMethod = getXMethod();
if (xMethod != null) {
String[] tes = xMethod.getThrownExceptions();
Set thrownExceptions = new HashSet<>(Arrays. asList((tes == null) ? new String[0] : tes));
blocks = new ArrayList<>();
inBlocks = new ArrayList<>();
transitionPoints = new BitSet();
CodeException[] ces = obj.getExceptionTable();
for (CodeException ce : ces) {
TryBlock tb = new TryBlock(ce);
int existingBlock = blocks.indexOf(tb);
if (existingBlock >= 0) {
tb = blocks.get(existingBlock);
tb.addCatchType(ce);
} else {
blocks.add(tb);
}
}
Iterator it = blocks.iterator();
while (it.hasNext()) {
TryBlock block = it.next();
if (block.hasMultipleHandlers() || block.isFinally() || block.catchIsThrown(getConstantPool(), thrownExceptions)) {
it.remove();
}
}
if (blocks.size() > 1) {
stack.resetForMethodEntry(this);
super.visitCode(obj);
if (blocks.size() > 1) {
TryBlock firstBlock = blocks.get(0);
for (int i = 1; i < blocks.size(); i++) {
TryBlock secondBlock = blocks.get(i);
if (!blocksSplitAcrossTransitions(firstBlock, secondBlock) && (firstBlock.getCatchType() == secondBlock.getCatchType())
&& firstBlock.getThrowSignature().equals(secondBlock.getThrowSignature())
&& firstBlock.getMessage().equals(secondBlock.getMessage())
&& firstBlock.getExceptionSignature().equals(secondBlock.getExceptionSignature())) {
bugReporter.reportBug(new BugInstance(this, BugType.STB_STACKED_TRY_BLOCKS.name(), NORMAL_PRIORITY).addClass(this)
.addMethod(this).addSourceLineRange(this, firstBlock.getStartPC(), firstBlock.getEndHandlerPC())
.addSourceLineRange(this, secondBlock.getStartPC(), secondBlock.getEndHandlerPC()));
}
firstBlock = secondBlock;
}
}
}
}
} finally {
blocks = null;
inBlocks = null;
transitionPoints = null;
}
}
/**
* overrides the visitor to document what catch blocks do with regard to rethrowing the exceptions, and if the message is a static message
*
* @param seen
* the currently parsed opcode
*/
@Override
public void sawOpcode(int seen) {
String message = null;
try {
stack.precomputation(this);
if ((seen == TABLESWITCH) || (seen == LOOKUPSWITCH)) {
int pc = getPC();
for (int offset : getSwitchOffsets()) {
transitionPoints.set(pc + offset);
}
transitionPoints.set(pc + getDefaultSwitchOffset());
} else if (isBranch(seen) && (getBranchOffset() < 0)) {
// throw out try blocks in loops, this could cause false
// negatives
// with two try/catches in one loop, but more unlikely
Iterator it = blocks.iterator();
int target = getBranchTarget();
while (it.hasNext()) {
TryBlock block = it.next();
if (block.getStartPC() >= target) {
it.remove();
}
}
}
int pc = getPC();
TryBlock block = findBlockWithStart(pc);
if (block != null) {
inBlocks.add(block);
block.setState(TryBlock.State.IN_TRY);
}
if (inBlocks.isEmpty()) {
return;
}
TryBlock innerBlock = inBlocks.get(inBlocks.size() - 1);
int nextPC = getNextPC();
if (innerBlock.atHandlerPC(nextPC)) {
if ((seen == GOTO) || (seen == GOTO_W)) {
innerBlock.setEndHandlerPC(getBranchTarget());
} else {
inBlocks.remove(innerBlock);
blocks.remove(innerBlock);
}
} else if (innerBlock.atHandlerPC(pc)) {
innerBlock.setState(TryBlock.State.IN_CATCH);
} else if (innerBlock.atEndHandlerPC(pc)) {
inBlocks.remove(inBlocks.size() - 1);
innerBlock.setState(TryBlock.State.AFTER);
}
if (transitionPoints.get(nextPC)) {
if (innerBlock.inCatch() && (innerBlock.getEndHandlerPC() > nextPC)) {
innerBlock.setEndHandlerPC(nextPC);
}
}
if (innerBlock.inCatch()) {
if (((seen >= Constants.IFEQ) && ((seen <= Constants.RET))) || (seen == GOTO_W) || OpcodeUtils.isReturn(seen)) {
blocks.remove(innerBlock);
inBlocks.remove(inBlocks.size() - 1);
} else if (seen == ATHROW) {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
XMethod xm = item.getReturnValueOf();
if (xm != null) {
innerBlock.setThrowSignature(xm.getSignature());
}
innerBlock.setExceptionSignature(item.getSignature());
innerBlock.setMessage((String) item.getUserValue());
} else {
inBlocks.remove(inBlocks.size() - 1);
innerBlock.setState(TryBlock.State.AFTER);
}
} else if ((seen == INVOKESPECIAL) && Values.CONSTRUCTOR.equals(getNameConstantOperand())) {
String cls = getClassConstantOperand();
JavaClass exCls = Repository.lookupClass(cls);
if (exCls.instanceOf(THROWABLE_CLASS)) {
String signature = getSigConstantOperand();
List types = SignatureUtils.getParameterSignatures(signature);
if (!types.isEmpty()) {
if (Values.SIG_JAVA_LANG_STRING.equals(types.get(0)) && (stack.getStackDepth() >= types.size())) {
OpcodeStack.Item item = stack.getStackItem(types.size() - 1);
message = (String) item.getConstant();
if (message == null) {
message = "____UNKNOWN____" + System.identityHashCode(item);
}
}
} else {
message = "";
}
}
}
}
} catch (ClassNotFoundException cnfe) {
bugReporter.reportMissingClass(cnfe);
} finally {
stack.sawOpcode(this, seen);
if ((message != null) && (stack.getStackDepth() > 0)) {
OpcodeStack.Item item = stack.getStackItem(0);
item.setUserValue(message);
}
}
}
/**
* looks for an existing try block that has this pc as a start of the try
*
* @param pc
* the current program counter
* @return the tryblock if this statement starts it, else null
*/
@Nullable
private TryBlock findBlockWithStart(int pc) {
for (TryBlock block : blocks) {
if (block.atStartPC(pc)) {
return block;
}
}
return null;
}
private boolean blocksSplitAcrossTransitions(TryBlock firstBlock, TryBlock secondBlock) {
if (!transitionPoints.isEmpty()) {
int transitionPoint = transitionPoints.nextSetBit(0);
while (transitionPoint >= 0) {
if (transitionPoint < firstBlock.handlerPC) {
transitionPoints.clear(transitionPoint);
} else {
return transitionPoint < secondBlock.handlerPC;
}
transitionPoint = transitionPoints.nextSetBit(transitionPoint + 1);
}
}
return false;
}
static class TryBlock {
enum State {
BEFORE, IN_TRY, IN_CATCH, AFTER
};
private int startPC;
private int endPC;
private int handlerPC;
private int endHandlerPC;
private BitSet catchTypes;
private String exSig;
private String throwSig;
private String message;
private State state;
TryBlock(CodeException ce) {
startPC = ce.getStartPC();
endPC = ce.getEndPC();
handlerPC = ce.getHandlerPC();
endHandlerPC = -1;
catchTypes = new BitSet();
catchTypes.set(ce.getCatchType());
state = State.BEFORE;
}
void addCatchType(CodeException ce) {
catchTypes.set(ce.getCatchType());
}
void setState(State executionState) {
state = executionState;
}
boolean inCatch() {
return state == State.IN_CATCH;
}
boolean hasMultipleHandlers() {
int bit = catchTypes.nextSetBit(0);
return catchTypes.nextSetBit(bit + 1) >= 0;
}
boolean isFinally() {
return catchTypes.get(0);
}
boolean catchIsThrown(ConstantPool pool, Set thrownExceptions) {
if (thrownExceptions.isEmpty()) {
return false;
}
int exIndex = catchTypes.nextSetBit(0);
String exName = ((ConstantClass) pool.getConstant(exIndex)).getBytes(pool);
return thrownExceptions.contains(exName);
}
void setEndHandlerPC(int end) {
endHandlerPC = end;
}
void setExceptionSignature(String sig) {
exSig = sig;
}
void setThrowSignature(String sig) {
throwSig = sig;
}
void setMessage(String m) {
message = m;
}
String getExceptionSignature() {
return (exSig == null) ? String.valueOf(System.identityHashCode(this)) : exSig;
}
String getThrowSignature() {
return (throwSig == null) ? String.valueOf(System.identityHashCode(this)) : throwSig;
}
String getMessage() {
return (message == null) ? String.valueOf(System.identityHashCode(this)) : message;
}
int getStartPC() {
return startPC;
}
int getEndHandlerPC() {
return endHandlerPC;
}
boolean atStartPC(int pc) {
return startPC == pc;
}
boolean atHandlerPC(int pc) {
return handlerPC == pc;
}
boolean atEndHandlerPC(int pc) {
return (endHandlerPC >= 0) && (endHandlerPC == pc);
}
int getCatchType() {
return catchTypes.nextSetBit(0);
}
@Override
public int hashCode() {
return startPC ^ endPC;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof TryBlock)) {
return false;
}
TryBlock that = (TryBlock) o;
return (startPC == that.startPC) && (endPC == that.endPC);
}
@Override
public String toString() {
return ToString.build(this);
}
}
}