/*
* 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.io.InputStreamReader;
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.XField;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
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 BugReporter bugReporter;
private final BugAccumulator bugAccumulator;
private int lastPC;
private BitSet potentiallyDeadStores = new BitSet();
private 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.bugReporter = bugReporter;
this.bugAccumulator = new BugAccumulator(bugReporter);
}
@Override
public void visitClassContext(ClassContext classContext) {
classContext.getJavaClass().accept(this);
}
Collection<SourceLineAnnotation> found = new LinkedList<SourceLineAnnotation>();
Collection<SourceLineAnnotation> foundDefault = new LinkedList<SourceLineAnnotation>();
@Override
public void visit(Code obj) {
reachable = false;
lastPC = 0;
found.clear();
foundDefault.clear();
switchHdlr = new SwitchHandler();
clearAll();
deadStore = null;
priority = NORMAL_PRIORITY;
fallthroughDistance = 1000;
super.visit(obj);
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);
}
for (SourceLineAnnotation s : foundDefault) {
LineNumberTable table = obj.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)
continue;
}
bugAccumulator.accumulateBug(new BugInstance(this, "SF_SWITCH_NO_DEFAULT", LOW_PRIORITY).addClassAndMethod(this), s);
}
bugAccumulator.reportAccumulatedBugs();
}
@Override
public void sawOpcode(int seen) {
boolean isDefaultOffset = switchHdlr.getDefaultOffset() == getPC();
boolean isCaseOffset = switchHdlr.isOnSwitchOffset(this);
if (DEBUG)
System.out
.println(getPC() + ": " + OPCODE_NAMES[seen] + " " + reachable + " " + isCaseOffset + " " + isDefaultOffset);
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, getPC(), getPC());
foundDefault.add(sourceLineAnnotation);
} else {
SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstructionRange(
getClassContext(), this, lastPC, getPC());
found.add(sourceLineAnnotation);
}
}
}
if (isBranch(seen) || isSwitch(seen) || seen == GOTO || seen == ARETURN || seen == IRETURN || seen == RETURN
|| seen == LRETURN || seen == DRETURN || seen == FRETURN) {
clearAll();
}
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(), 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);
}
}
clearAll();
}
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);
}
switch (seen) {
case TABLESWITCH:
case LOOKUPSWITCH:
reachable = false;
switchHdlr.enterSwitch(this);
break;
case ATHROW:
case RETURN:
case ARETURN:
case IRETURN:
case LRETURN:
case DRETURN:
case FRETURN:
case GOTO_W:
case GOTO:
reachable = false;
break;
case INVOKESTATIC:
reachable = !("exit".equals(getNameConstantOperand()) && "java/lang/System".equals(getClassConstantOperand()));
break;
default:
reachable = true;
}
lastPC = getPC();
fallthroughDistance++;
}
/**
*
*/
private void clearAll() {
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 = new BufferedReader(new InputStreamReader(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;
}
}