001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2003-2005 University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.detect;
021:
022: import java.io.BufferedReader;
023: import java.io.IOException;
024: import java.io.InputStreamReader;
025: import java.util.BitSet;
026: import java.util.Collection;
027: import java.util.LinkedList;
028:
029: import org.apache.bcel.classfile.Code;
030:
031: import edu.umd.cs.findbugs.BugInstance;
032: import edu.umd.cs.findbugs.BugReporter;
033: import edu.umd.cs.findbugs.BytecodeScanningDetector;
034: import edu.umd.cs.findbugs.LocalVariableAnnotation;
035: import edu.umd.cs.findbugs.SourceLineAnnotation;
036: import edu.umd.cs.findbugs.StatelessDetector;
037: import edu.umd.cs.findbugs.SwitchHandler;
038: import edu.umd.cs.findbugs.SystemProperties;
039: import edu.umd.cs.findbugs.ba.AnalysisContext;
040: import edu.umd.cs.findbugs.ba.ClassContext;
041: import edu.umd.cs.findbugs.ba.SourceFile;
042: import edu.umd.cs.findbugs.ba.SourceFinder;
043:
044: public class SwitchFallthrough extends BytecodeScanningDetector
045: implements StatelessDetector {
046: private static final boolean DEBUG = SystemProperties
047: .getBoolean("switchFallthrough.debug");
048: private static final boolean LOOK_IN_SOURCE_FOR_FALLTHRU_COMMENT = SystemProperties
049: .getBoolean("findbugs.sf.comment");
050:
051: private SwitchHandler switchHdlr;
052: private boolean reachable;
053: private BugReporter bugReporter;
054: private int lastPC;
055: private BitSet potentiallyDeadStores = new BitSet();
056: private BitSet potentiallyDeadStoresFromBeforeFallthrough = new BitSet();
057: private LocalVariableAnnotation deadStore = null;
058: private int priority;
059: private int fallthroughDistance;
060:
061: public SwitchFallthrough(BugReporter bugReporter) {
062: this .bugReporter = bugReporter;
063: }
064:
065: @Override
066: public void visitClassContext(ClassContext classContext) {
067: classContext.getJavaClass().accept(this );
068: }
069:
070: Collection<SourceLineAnnotation> found = new LinkedList<SourceLineAnnotation>();
071:
072: @Override
073: public void visit(Code obj) {
074: reachable = false;
075: lastPC = 0;
076: found.clear();
077: switchHdlr = new SwitchHandler();
078: potentiallyDeadStores.clear();
079: deadStore = null;
080: potentiallyDeadStoresFromBeforeFallthrough.clear();
081: priority = NORMAL_PRIORITY;
082: fallthroughDistance = 1000;
083: super .visit(obj);
084: if (!found.isEmpty()) {
085: if (found.size() >= 4 && priority == NORMAL_PRIORITY)
086: priority = LOW_PRIORITY;
087: BugInstance bug = new BugInstance(this ,
088: "SF_SWITCH_FALLTHROUGH", priority)
089: .addClassAndMethod(this ).addAnnotations(found);
090: bugReporter.reportBug(bug);
091:
092: }
093: }
094:
095: @Override
096: public void sawOpcode(int seen) {
097: if (DEBUG)
098: System.out.println(getPC() + ": " + OPCODE_NAMES[seen]
099: + " " + reachable + " "
100: + switchHdlr.isOnSwitchOffset(this ));
101:
102: if (reachable && switchHdlr.isOnSwitchOffset(this )) {
103: if (DEBUG) {
104: System.out.println("Fallthrough at : " + getPC() + ": "
105: + OPCODE_NAMES[seen]);
106: }
107: fallthroughDistance = 0;
108: potentiallyDeadStoresFromBeforeFallthrough = (BitSet) potentiallyDeadStores
109: .clone();
110: if (!hasFallThruComment(lastPC + 1, getPC() - 1)) {
111: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
112: .fromVisitedInstructionRange(getClassContext(),
113: this , lastPC, getPC());
114: if (sourceLineAnnotation != null) {
115: found.add(sourceLineAnnotation);
116: }
117: }
118:
119: }
120:
121: if (isBranch(seen) || isSwitch(seen) || seen == GOTO
122: || seen == ARETURN || seen == IRETURN || seen == RETURN
123: || seen == LRETURN || seen == DRETURN
124: || seen == FRETURN) {
125: potentiallyDeadStores.clear();
126: potentiallyDeadStoresFromBeforeFallthrough.clear();
127: }
128:
129: if (isRegisterLoad())
130: potentiallyDeadStores.clear(getRegisterOperand());
131:
132: else if (isRegisterStore() && !atCatchBlock()) {
133: int register = getRegisterOperand();
134: if (potentiallyDeadStores.get(register)
135: && (potentiallyDeadStoresFromBeforeFallthrough
136: .get(register))) {
137: // killed store
138: priority = HIGH_PRIORITY;
139: deadStore = LocalVariableAnnotation
140: .getLocalVariableAnnotation(getMethod(),
141: register, getPC() - 1, getPC());
142: BugInstance bug = new BugInstance(this ,
143: "SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH",
144: priority).addClassAndMethod(this )
145: .add(deadStore).addSourceLine(this );
146: bugReporter.reportBug(bug);
147:
148: }
149: potentiallyDeadStores.set(register);
150: }
151:
152: switch (seen) {
153: case TABLESWITCH:
154: case LOOKUPSWITCH:
155: reachable = false;
156: switchHdlr.enterSwitch(this );
157: break;
158:
159: case ATHROW:
160: case RETURN:
161: case ARETURN:
162: case IRETURN:
163: case LRETURN:
164: case DRETURN:
165: case FRETURN:
166: case GOTO_W:
167: case GOTO:
168: reachable = false;
169: break;
170:
171: case INVOKESTATIC:
172: reachable = !("exit".equals(getNameConstantOperand()) && "java/lang/System"
173: .equals(getClassConstantOperand()));
174: break;
175:
176: default:
177: reachable = true;
178: }
179:
180: lastPC = getPC();
181: fallthroughDistance++;
182: }
183:
184: private boolean hasFallThruComment(int startPC, int endPC) {
185: if (LOOK_IN_SOURCE_FOR_FALLTHRU_COMMENT) {
186: BufferedReader r = null;
187: try {
188: SourceLineAnnotation srcLine = SourceLineAnnotation
189: .fromVisitedInstructionRange(this , lastPC,
190: getPC());
191: SourceFinder sourceFinder = AnalysisContext
192: .currentAnalysisContext().getSourceFinder();
193: SourceFile sourceFile = sourceFinder.findSourceFile(
194: srcLine.getPackageName(), srcLine
195: .getSourceFile());
196:
197: int startLine = srcLine.getStartLine();
198: int numLines = srcLine.getEndLine() - startLine - 1;
199: if (numLines <= 0)
200: return false;
201: r = new BufferedReader(new InputStreamReader(sourceFile
202: .getInputStream()));
203: for (int i = 0; i < startLine; i++) {
204: String line = r.readLine();
205: if (line == null)
206: return false;
207: }
208: for (int i = 0; i < numLines; i++) {
209: String line = r.readLine();
210: if (line == null)
211: return false;
212: line = line.toLowerCase();
213: if (line.indexOf("fall") >= 0
214: || line.indexOf("nobreak") >= 0) {
215: return true;
216: }
217: }
218: } catch (IOException ioe) {
219: //Problems with source file, mean report the bug
220: } finally {
221: try {
222: if (r != null)
223: r.close();
224: } catch (IOException ioe) {
225: }
226: }
227: }
228: return false;
229: }
230: }
|