/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2005 Dave Brosius <dbrosius@users.sourceforge.net>
* Copyright (C) 2005-2006 University of Maryland
*
* 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 edu.umd.cs.findbugs.detect;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.GotoInstruction;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.LOOKUPSWITCH;
import org.apache.bcel.generic.ReturnInstruction;
import org.apache.bcel.generic.TABLESWITCH;
import org.apache.bcel.util.ByteSequence;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.EdgeTypes;
import edu.umd.cs.findbugs.ba.MethodUnprofitableException;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
/**
* @author Dave Brousius 4/2005 original author
* @author Brian Cole 7/2006 serious reworking
*/
public class DuplicateBranches extends PreorderVisitor implements Detector {
private ClassContext classContext;
private BugReporter bugReporter;
private Collection<BugInstance> pendingBugs = new LinkedList<BugInstance>();
public DuplicateBranches(BugReporter bugReporter) {
this.bugReporter = bugReporter;
}
public void visitClassContext(ClassContext classContext) {
this.classContext = classContext;
classContext.getJavaClass().accept(this);
}
@Override
public void visitMethod(Method method) {
try {
if (method.getCode() == null)
return;
CFG cfg = classContext.getCFG(method);
Iterator<BasicBlock> bbi = cfg.blockIterator();
while (bbi.hasNext()) {
BasicBlock bb = bbi.next();
int numOutgoing = cfg.getNumOutgoingEdges(bb);
if (numOutgoing == 2)
findIfElseDuplicates(cfg, method, bb);
else if (numOutgoing > 2)
findSwitchDuplicates(cfg, method, bb);
}
} catch (MethodUnprofitableException mue) {
if (SystemProperties.getBoolean("unprofitable.debug")) // otherwise
// don't
// report
bugReporter.logError("skipping unprofitable method in " + getClass().getName());
} catch (Exception e) {
bugReporter.logError("Failure examining basic blocks in Duplicate Branches detector", e);
}
if (pendingBugs.size() <= 2)
for (BugInstance b : pendingBugs)
bugReporter.reportBug(b);
pendingBugs.clear();
}
private void findIfElseDuplicates(CFG cfg, Method method, BasicBlock bb) {
BasicBlock thenBB = null, elseBB = null;
Iterator<Edge> iei = cfg.outgoingEdgeIterator(bb);
while (iei.hasNext()) {
Edge e = iei.next();
if (e.getType() == EdgeTypes.IFCMP_EDGE) {
elseBB = e.getTarget();
} else if (e.getType() == EdgeTypes.FALL_THROUGH_EDGE) {
thenBB = e.getTarget();
}
}
if ((thenBB == null) || (elseBB == null))
return;
InstructionHandle thenStartHandle = getDeepFirstInstruction(cfg, thenBB);
InstructionHandle elseStartHandle = getDeepFirstInstruction(cfg, elseBB);
if ((thenStartHandle == null) || (elseStartHandle == null))
return;
int thenStartPos = thenStartHandle.getPosition();
int elseStartPos = elseStartHandle.getPosition();
InstructionHandle thenFinishIns = findThenFinish(cfg, thenBB, elseStartPos);
int thenFinishPos = thenFinishIns.getPosition();
if (!(thenFinishIns.getInstruction() instanceof GotoInstruction))
return;
InstructionHandle elseFinishHandle = ((GotoInstruction) thenFinishIns.getInstruction()).getTarget();
int elseFinishPos = elseFinishHandle.getPosition();
if (thenFinishPos >= elseStartPos)
return;
if ((thenFinishPos - thenStartPos) != (elseFinishPos - elseStartPos))
return;
if (thenFinishPos <= thenStartPos)
return;
byte[] thenBytes = getCodeBytes(method, thenStartPos, thenFinishPos);
byte[] elseBytes = getCodeBytes(method, elseStartPos, elseFinishPos);
if (!Arrays.equals(thenBytes, elseBytes))
return;
// adjust elseFinishPos to be inclusive (for source line attribution)
InstructionHandle elseLastIns = elseFinishHandle.getPrev();
if (elseLastIns != null)
elseFinishPos = elseLastIns.getPosition();
pendingBugs.add(new BugInstance(this, "DB_DUPLICATE_BRANCHES", NORMAL_PRIORITY)
.addClassAndMethod(classContext.getJavaClass(), method)
.addSourceLineRange(classContext, this, thenStartPos, thenFinishPos)
.addSourceLineRange(classContext, this, elseStartPos, elseFinishPos));
}
/**
* Like bb.getFirstInstruction() except that if null is returned it will
* follow the FALL_THROUGH_EDGE (if any)
*/
private static InstructionHandle getDeepFirstInstruction(CFG cfg, BasicBlock bb) {
InstructionHandle ih = bb.getFirstInstruction();
if (ih != null)
return ih;
Iterator<Edge> iei = cfg.outgoingEdgeIterator(bb);
while (iei.hasNext()) {
Edge e = iei.next();
String edgeString = e.toString();
if (EdgeTypes.FALL_THROUGH_EDGE == e.getType())
return getDeepFirstInstruction(cfg, e.getTarget());
}
return null;
}
private void findSwitchDuplicates(CFG cfg, Method method, BasicBlock bb) {
int[] switchPos = new int[cfg.getNumOutgoingEdges(bb) + 1];
HashMap<Integer, InstructionHandle> prevHandle = new HashMap<Integer, InstructionHandle>();
Iterator<Edge> iei = cfg.outgoingEdgeIterator(bb);
int idx = 0;
while (iei.hasNext()) {
Edge e = iei.next();
int eType = e.getType();
if (eType == EdgeTypes.SWITCH_EDGE || eType == EdgeTypes.SWITCH_DEFAULT_EDGE) {
BasicBlock target = e.getTarget();
InstructionHandle firstIns = getDeepFirstInstruction(cfg, target);
if (firstIns == null)
continue; // give up on this edge
int firstInsPosition = firstIns.getPosition();
switchPos[idx++] = firstInsPosition;
InstructionHandle prevIns = firstIns.getPrev(); // prev in
// bytecode, not
// flow
if (prevIns != null)
prevHandle.put(firstInsPosition, prevIns);
} else {
// hmm, this must not be a switch statement, so give up
return;
}
}
if (idx < 2) // need at least two edges to tango
return;
Arrays.sort(switchPos, 0, idx); // sort the 'idx' switch positions
// compute end position of final case (ok if set to 0 or <=
// switchPos[idx-1])
switchPos[idx] = getFinalTarget(cfg, switchPos[idx - 1], prevHandle.values());
HashMap<BigInteger, Collection<Integer>> map = new HashMap<BigInteger, Collection<Integer>>();
for (int i = 0; i < idx; i++) {
if (switchPos[i] + 7 >= switchPos[i + 1])
continue; // ignore small switch clauses
int endPos = switchPos[i + 1];
InstructionHandle last = prevHandle.get(switchPos[i + 1]);
if (last == null) {
// should be default case -- leave endPos as is
} else if (last.getInstruction() instanceof GotoInstruction) {
endPos = last.getPosition(); // don't store the goto
} else if (last.getInstruction() instanceof ReturnInstruction) {
// leave endPos as is (store the return instruction)
// } else if (last.getInstruction() instanceof ATHROW) {
// // leave endPos as is (store the throw instruction)
// Don't do this since many cases may throw "not implemented".
} else {
if (i + 2 < idx)
continue; // falls through to next case, so don't store it
// at all
if (i + 1 < idx && switchPos[idx] != switchPos[idx - 1])
continue; // also falls through unless switch has no default
// case
}
BigInteger clauseAsInt = getCodeBytesAsBigInt(method, switchPos, i, endPos);
updateMap(map, i, clauseAsInt);
}
for (Collection<Integer> clauses : map.values()) {
if (clauses.size() > 1) {
BugInstance bug = new BugInstance(this, "DB_DUPLICATE_SWITCH_CLAUSES", LOW_PRIORITY).addClassAndMethod(
classContext.getJavaClass(), method);
for (int i : clauses)
bug.addSourceLineRange(this.classContext, this, switchPos[i], switchPos[i + 1] - 1); // not
// endPos,
// but
// that's
// ok
pendingBugs.add(bug);
}
}
}
private void updateMap(HashMap<BigInteger, Collection<Integer>> map, int i, BigInteger clauseAsInt) {
Collection<Integer> values = map.get(clauseAsInt);
if (values == null) {
values = new LinkedList<Integer>();
map.put(clauseAsInt, values);
}
values.add(i); // index into the sorted array
}
private BigInteger getCodeBytesAsBigInt(Method method, int[] switchPos, int i, int endPos) {
byte[] clause = getCodeBytes(method, switchPos[i], endPos);
BigInteger clauseAsInt;
if (clause.length == 0)
clauseAsInt = BigInteger.ZERO;
else
clauseAsInt = new BigInteger(clause);
return clauseAsInt;
}
/**
* determine the end position (exclusive) of the final case by looking at
* the gotos at the ends of the other cases
*/
private static int getFinalTarget(CFG cfg, int myPos, Collection<InstructionHandle> prevs) {
int maxGoto = 0;
BasicBlock myBB = null;
// note: InstructionHandle doesn't override equals(), so use
// prevs.contains() with caution.
Iterator<BasicBlock> bbi = cfg.blockIterator();
while (bbi.hasNext()) {
BasicBlock bb = bbi.next();
InstructionHandle last = bb.getLastInstruction(); // may be null
if (prevs.contains(last)) { // danger will robinson
Iterator<Edge> iei = cfg.outgoingEdgeIterator(bb);
while (iei.hasNext()) {
Edge e = iei.next();
int eType = e.getType();
String aab = e.toString();
if (eType == EdgeTypes.GOTO_EDGE) {
BasicBlock target = e.getTarget();
InstructionHandle targetFirst = getDeepFirstInstruction(cfg, target);
if (targetFirst != null) {
int targetPos = targetFirst.getPosition();
if (targetPos > maxGoto)
maxGoto = targetPos;
}
}
}
} else if (last != null && myPos == bb.getFirstInstruction().getPosition()) {
// note: getFirstInstruction() may return null, but won't if
// last!=null.
myBB = bb; // used in case (c) below
}
}
/*
* ok, there are three cases: a) maxGoto == myPos: There is no default
* case within the switch. b) maxGoto > myPos: maxGoto is the end
* (exclusive) of the default case c) maxGoto < myPos: all the cases do
* something like return or throw, so who knows if there is a default
* case (and it's length)?
*/
if (maxGoto < myPos && myBB != null) {
/*
* We're in case (c), so let's guess that the end of the basic block
* is the end of the default case (if it exists). This may give
* false negatives (if the default case has branches, for example)
* but shouldn't give false negatives (because if it matches one of
* the cases, then it has also matched that case's return/throw).
*/
InstructionHandle last = myBB.getLastInstruction();
if (last != null) {
// note: last.getNext() may return null, so do it this way
return last.getPosition() + last.getInstruction().getLength();
}
}
return maxGoto;
}
private byte[] getCodeBytes(Method m, int start, int end) {
byte[] code = m.getCode().getCode();
byte[] bytes = new byte[end - start];
System.arraycopy(code, start, bytes, 0, end - start);
try {
ByteSequence sequence = new ByteSequence(code);
while ((sequence.available() > 0) && (sequence.getIndex() < start)) {
Instruction.readInstruction(sequence);
}
int pos;
while (sequence.available() > 0 && ((pos = sequence.getIndex()) < end)) {
Instruction ins = Instruction.readInstruction(sequence);
if ((ins instanceof BranchInstruction) && !(ins instanceof TABLESWITCH) && !(ins instanceof LOOKUPSWITCH)) {
BranchInstruction bi = (BranchInstruction) ins;
int offset = bi.getIndex();
int target = offset + pos;
if (target >= end) { // or target < start ??
byte hiByte = (byte) ((target >> 8) & 0x000000FF);
byte loByte = (byte) (target & 0x000000FF);
bytes[pos + bi.getLength() - 2 - start] = hiByte;
bytes[pos + bi.getLength() - 1 - start] = loByte;
}
}
}
} catch (IOException ioe) {
}
return bytes;
}
private InstructionHandle findThenFinish(CFG cfg, BasicBlock thenBB, int elsePos) {
InstructionHandle inst = thenBB.getFirstInstruction();
while (inst == null) {
Iterator<Edge> ie = cfg.outgoingEdgeIterator(thenBB);
while (ie.hasNext()) {
Edge e = ie.next();
if (e.getType() == EdgeTypes.FALL_THROUGH_EDGE) {
thenBB = e.getTarget();
break;
}
}
inst = thenBB.getFirstInstruction();
}
InstructionHandle lastIns = inst;
while (inst.getPosition() < elsePos) {
lastIns = inst;
inst = inst.getNext();
}
return lastIns;
}
public void report() {
}
}