/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2003-2005 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.BufferedReader;
import java.io.IOException;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.LineNumber;
import org.apache.bcel.classfile.LineNumberTable;
import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.LocalVariableAnnotation;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.StatelessDetector;
import edu.umd.cs.findbugs.SwitchHandler;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.SourceFile;
import edu.umd.cs.findbugs.ba.SourceFinder;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.charsets.UTF8;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
public class SwitchFallthrough extends OpcodeStackDetector implements StatelessDetector {
private static final boolean DEBUG = SystemProperties.getBoolean("switchFallthrough.debug");
private static final boolean LOOK_IN_SOURCE_FOR_FALLTHRU_COMMENT = SystemProperties.getBoolean("findbugs.sf.comment");
private SwitchHandler switchHdlr;
private boolean reachable;
private final BugAccumulator bugAccumulator;
private int lastPC;
private int biggestJumpTarget;
private final BitSet potentiallyDeadStores = new BitSet();
private final Set<XField> potentiallyDeadFields = new HashSet<XField>();
private BitSet potentiallyDeadStoresFromBeforeFallthrough = new BitSet();
private Set<XField> potentiallyDeadFieldsFromBeforeFallthrough = new HashSet<XField>();
private LocalVariableAnnotation deadStore = null;
private int priority;
private int fallthroughDistance;
public SwitchFallthrough(BugReporter bugReporter) {
this.bugAccumulator = new BugAccumulator(bugReporter);
}
@Override
public void visitClassContext(ClassContext classContext) {
classContext.getJavaClass().accept(this);
}
Collection<SourceLineAnnotation> found = new LinkedList<SourceLineAnnotation>();
@Override
public void visit(Code obj) {
if (DEBUG)
System.out.printf("%nVisiting %s%n", getMethodDescriptor());
reachable = false;
lastPC = 0;
biggestJumpTarget = -1;
found.clear();
switchHdlr = new SwitchHandler();
clearAllDeadStores();
deadStore = null;
priority = NORMAL_PRIORITY;
fallthroughDistance = 1000;
enumType = null;
super.visit(obj);
enumType = null;
if (!found.isEmpty()) {
if (found.size() >= 4 && priority == NORMAL_PRIORITY)
priority = LOW_PRIORITY;
for (SourceLineAnnotation s : found)
bugAccumulator.accumulateBug(new BugInstance(this, "SF_SWITCH_FALLTHROUGH", priority).addClassAndMethod(this), s);
}
bugAccumulator.reportAccumulatedBugs();
}
private void foundSwitchNoDefault(SourceLineAnnotation s) {
LineNumberTable table = getCode().getLineNumberTable();
if (table != null) {
int startLine = s.getStartLine();
int prev = Integer.MIN_VALUE;
for (LineNumber ln : table.getLineNumberTable()) {
int thisLineNumber = ln.getLineNumber();
if (thisLineNumber < startLine && thisLineNumber > prev && ln.getStartPC() < s.getStartBytecode())
prev = thisLineNumber;
}
int diff = startLine - prev;
if (diff > 5)
return;
bugAccumulator.accumulateBug(new BugInstance(this, "SF_SWITCH_NO_DEFAULT", NORMAL_PRIORITY).addClassAndMethod(this),
s);
}
}
XClass enumType = null;
@Override
public void sawOpcode(int seen) {
boolean isDefaultOffset = switchHdlr.getDefaultOffset() == getPC();
boolean isCaseOffset = switchHdlr.isOnSwitchOffset(this);
if (DEBUG) {
if (seen == GOTO)
System.out.printf("%4d: goto %-7d %s %s %s %d%n", getPC(), getBranchTarget(), reachable, isCaseOffset,
isDefaultOffset, switchHdlr.stackSize());
else
System.out.printf("%4d: %-12s %s %s %s %d%n", getPC(), OPCODE_NAMES[seen], reachable, isCaseOffset,
isDefaultOffset, switchHdlr.stackSize());
}
if (reachable && (isDefaultOffset || isCaseOffset)) {
if (DEBUG) {
System.out.println("Fallthrough at : " + getPC() + ": " + OPCODE_NAMES[seen]);
}
fallthroughDistance = 0;
potentiallyDeadStoresFromBeforeFallthrough = (BitSet) potentiallyDeadStores.clone();
potentiallyDeadFieldsFromBeforeFallthrough = new HashSet<XField>(potentiallyDeadFields);
if (!hasFallThruComment(lastPC + 1, getPC() - 1)) {
if (!isDefaultOffset) {
SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstructionRange(
getClassContext(), this, lastPC, getPC());
found.add(sourceLineAnnotation);
} else if ( getPC() >= biggestJumpTarget) {
SourceLineAnnotation sourceLineAnnotation = switchHdlr.getCurrentSwitchStatement(this);
if (DEBUG)
System.out.printf("Found fallthrough to default offset at %d (BJT is %d)%n", getPC(), biggestJumpTarget);
foundSwitchNoDefault(sourceLineAnnotation);
}
}
}
if (isBranch(seen) || isSwitch(seen) || seen == GOTO || seen == ARETURN || seen == IRETURN || seen == RETURN
|| seen == LRETURN || seen == DRETURN || seen == FRETURN) {
clearAllDeadStores();
}
if (seen == GETFIELD && stack.getStackDepth() > 0) {
OpcodeStack.Item top = stack.getStackItem(0);
if (top.getRegisterNumber() == 0)
potentiallyDeadFields.remove(getXFieldOperand());
}
else if (seen == PUTFIELD && stack.getStackDepth() >= 2) {
OpcodeStack.Item obj = stack.getStackItem(1);
if (obj.getRegisterNumber() == 0) {
XField f = getXFieldOperand();
if (potentiallyDeadFields.contains(f) && potentiallyDeadFieldsFromBeforeFallthrough.contains(f)) {
// killed store
priority = HIGH_PRIORITY;
bugAccumulator.accumulateBug(new BugInstance(this, "SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH", priority)
.addClassAndMethod(this).addField(f), this);
}
potentiallyDeadFields.add(f);
}
}
if (seen == ATHROW) {
int sz = edu.umd.cs.findbugs.visitclass.Util.getSizeOfSurroundingTryBlock(getMethod(), (String) null, getPC());
if (sz == Integer.MAX_VALUE) {
BitSet dead = new BitSet();
dead.or(potentiallyDeadStores);
dead.and(potentiallyDeadStoresFromBeforeFallthrough);
if (dead.cardinality() > 0) {
int register = dead.nextSetBit(0);
priority = HIGH_PRIORITY;
deadStore = LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), register, getPC() - 1, getPC());
bugAccumulator.accumulateBug(new BugInstance(this, "SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH_TO_THROW",
priority).addClassAndMethod(this).add(deadStore), this);
}
}
clearAllDeadStores();
}
if (isRegisterLoad())
potentiallyDeadStores.clear(getRegisterOperand());
else if (isRegisterStore() && !atCatchBlock()) {
int register = getRegisterOperand();
if (potentiallyDeadStores.get(register) && (potentiallyDeadStoresFromBeforeFallthrough.get(register))) {
// killed store
priority = HIGH_PRIORITY;
deadStore = LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), register, getPC() - 1, getPC());
bugAccumulator.accumulateBug(new BugInstance(this, "SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH", priority)
.addClassAndMethod(this).add(deadStore), this);
}
potentiallyDeadStores.set(register);
}
if (seen == INVOKEVIRTUAL && getNameConstantOperand().equals("ordinal") && getSigConstantOperand().equals("()I")) {
XClass c = getXClassOperand();
if (c != null) {
ClassDescriptor superclassDescriptor = c.getSuperclassDescriptor();
if (superclassDescriptor != null && superclassDescriptor.getClassName().equals("java/lang/Enum"))
enumType = c;
if (DEBUG)
System.out.println("Saw " + enumType + ".ordinal()");
}
} else if (seen != TABLESWITCH && seen != LOOKUPSWITCH && seen != IALOAD)
enumType = null;
switch (seen) {
case TABLESWITCH:
case LOOKUPSWITCH:
if (justSawHashcode)
break; // javac compiled switch statement
reachable = false;
biggestJumpTarget = -1;
switchHdlr.enterSwitch(this, enumType);
if (DEBUG)
System.out.printf(" entered switch, default is %d%n", switchHdlr.getDefaultOffset());
break;
case GOTO_W:
case GOTO:
if (biggestJumpTarget < getBranchTarget()) {
biggestJumpTarget = getBranchTarget();
if (DEBUG)
System.out.printf(" Setting BJT to %d%n", biggestJumpTarget);
}
reachable = false;
break;
case ATHROW:
case RETURN:
case ARETURN:
case IRETURN:
case LRETURN:
case DRETURN:
case FRETURN:
reachable = false;
break;
case INVOKESTATIC:
reachable = !("exit".equals(getNameConstantOperand()) && "java/lang/System".equals(getClassConstantOperand()));
break;
default:
reachable = true;
}
justSawHashcode = seen == INVOKEVIRTUAL && getNameConstantOperand().equals("hashCode") && getSigConstantOperand().equals("()I");
lastPC = getPC();
fallthroughDistance++;
}
boolean justSawHashcode;
/**
*
*/
private void clearAllDeadStores() {
potentiallyDeadStores.clear();
potentiallyDeadStoresFromBeforeFallthrough.clear();
potentiallyDeadFields.clear();
potentiallyDeadFieldsFromBeforeFallthrough.clear();
}
private boolean hasFallThruComment(int startPC, int endPC) {
if (LOOK_IN_SOURCE_FOR_FALLTHRU_COMMENT) {
BufferedReader r = null;
try {
SourceLineAnnotation srcLine = SourceLineAnnotation.fromVisitedInstructionRange(this, lastPC, getPC());
SourceFinder sourceFinder = AnalysisContext.currentAnalysisContext().getSourceFinder();
SourceFile sourceFile = sourceFinder.findSourceFile(srcLine.getPackageName(), srcLine.getSourceFile());
int startLine = srcLine.getStartLine();
int numLines = srcLine.getEndLine() - startLine - 1;
if (numLines <= 0)
return false;
r = UTF8.bufferedReader(sourceFile.getInputStream());
for (int i = 0; i < startLine; i++) {
String line = r.readLine();
if (line == null)
return false;
}
for (int i = 0; i < numLines; i++) {
String line = r.readLine();
if (line == null)
return false;
line = line.toLowerCase();
if (line.indexOf("fall") >= 0 || line.indexOf("nobreak") >= 0) {
return true;
}
}
} catch (IOException ioe) {
// Problems with source file, mean report the bug
} finally {
try {
if (r != null)
r.close();
} catch (IOException ioe) {
}
}
}
return false;
}
}