KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > detect > DroppedException


1 /*
2  * FindBugs - Find bugs in Java programs
3  * Copyright (C) 2003-2005, University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs.detect;
21
22 import java.io.IOException JavaDoc;
23 import java.io.InputStreamReader JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.HashSet JavaDoc;
26 import java.util.ListIterator JavaDoc;
27 import java.util.Set JavaDoc;
28
29 import org.apache.bcel.classfile.Code;
30 import org.apache.bcel.classfile.CodeException;
31 import org.apache.bcel.classfile.LineNumber;
32 import org.apache.bcel.classfile.LineNumberTable;
33 import org.apache.bcel.classfile.Utility;
34
35 import edu.umd.cs.findbugs.BugInstance;
36 import edu.umd.cs.findbugs.BugReporter;
37 import edu.umd.cs.findbugs.Detector;
38 import edu.umd.cs.findbugs.SourceLineAnnotation;
39 import edu.umd.cs.findbugs.SystemProperties;
40 import edu.umd.cs.findbugs.Token;
41 import edu.umd.cs.findbugs.Tokenizer;
42 import edu.umd.cs.findbugs.ba.AnalysisContext;
43 import edu.umd.cs.findbugs.ba.ClassContext;
44 import edu.umd.cs.findbugs.ba.Hierarchy;
45 import edu.umd.cs.findbugs.ba.SourceFile;
46 import edu.umd.cs.findbugs.ba.SourceFinder;
47 import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
48
49 public class DroppedException extends PreorderVisitor implements Detector {
50     private static final boolean DEBUG = SystemProperties.getBoolean("de.debug");
51     private static final boolean LOOK_IN_SOURCE_TO_FIND_COMMENTED_CATCH_BLOCKS
52             = SystemProperties.getBoolean("findbugs.de.comment");
53
54     Set JavaDoc<String JavaDoc> reported = new HashSet JavaDoc<String JavaDoc>();
55     Set JavaDoc<String JavaDoc> causes = new HashSet JavaDoc<String JavaDoc>();
56     Set JavaDoc<String JavaDoc> checkedCauses = new HashSet JavaDoc<String JavaDoc>();
57     private BugReporter bugReporter;
58     private ClassContext classContext;
59
60     public DroppedException(BugReporter bugReporter) {
61         this.bugReporter = bugReporter;
62         if (DEBUG) System.out.println("Dropped Exception debugging turned on");
63     }
64
65     public void visitClassContext(ClassContext classContext) {
66         this.classContext = classContext;
67         classContext.getJavaClass().accept(this);
68     }
69
70     public void report() {
71     }
72
73     boolean isChecked(String JavaDoc c) {
74         if (!causes.add(c)) return checkedCauses.contains(c);
75         try {
76             if (Hierarchy.isSubtype(c, "java.lang.Exception")
77                     && !Hierarchy.isSubtype(c, "java.lang.RuntimeException")) {
78                 checkedCauses.add(c);
79                 return true;
80             }
81         } catch (ClassNotFoundException JavaDoc e) {
82             bugReporter.reportMissingClass(e);
83         }
84         return false;
85     }
86
87
88     private int getUnsignedShort(byte[] a, int i) {
89         return asUnsignedByte(a[i]) << 8
90                 | asUnsignedByte(a[i + 1]);
91     }
92
93     @Override JavaDoc
94          public void visit(Code obj) {
95
96         CodeException[] exp = obj.getExceptionTable();
97         LineNumberTable lineNumbers = obj.getLineNumberTable();
98         if (exp == null) return;
99         byte[] code = obj.getCode();
100
101         for (CodeException aExp : exp) {
102             int handled = aExp.getHandlerPC();
103             int start = aExp.getStartPC();
104             int end = aExp.getEndPC();
105             int cause = aExp.getCatchType();
106             boolean exitInTryBlock = false;
107             if (DEBUG) {
108                 System.out.println("start = " + start
109                         + ", end = " + end
110                         + ", codeLength = " + code.length
111                         + ", handled = " + handled);
112             }
113
114             for (int j = start; j <= end && j < code.length;) {
115                 int opcode = asUnsignedByte(code[j]);
116                 if (NO_OF_OPERANDS[opcode] < 0) {
117                     exitInTryBlock = true;
118                     break;
119                 }
120                 j += 1 + NO_OF_OPERANDS[opcode];
121                 if (opcode >= IRETURN && opcode <= RETURN
122                         || opcode >= IFEQ && opcode <= GOTO
123                             && (opcode != GOTO || j < end)
124                         ) {
125                     exitInTryBlock = true;
126                     if (DEBUG)
127                         System.out.println(" exit: " + opcode
128                                 + " in " + getFullyQualifiedMethodName());
129                     break;
130                 }
131             
132             }
133
134             if (exitInTryBlock) {
135                 if (DEBUG) System.out.println("Exit in try block");
136                 continue;
137             }
138             if (handled < 5) continue;
139             String JavaDoc c;
140             if (cause == 0)
141                 c = "Throwable";
142             else {
143                 c = Utility.compactClassName(getConstantPool().getConstantString(cause,
144                         CONSTANT_Class), false);
145                 if (!isChecked(c)) continue;
146             }
147
148
149             int jumpAtEnd = 0;
150             if (asUnsignedByte(code[end]) == GOTO) {
151                 jumpAtEnd = getUnsignedShort(code, end + 1);
152                 if (jumpAtEnd < handled) jumpAtEnd = 0;
153             }
154
155             int opcode = asUnsignedByte(code[handled]);
156             int afterHandler = 0;
157             if (DEBUG) System.out.println("DE: opcode is " + opcode + ", " + asUnsignedByte(code[handled + 1]));
158             boolean drops = false;
159             boolean startsWithASTORE03 = opcode >= ASTORE_0
160                     && opcode <= ASTORE_3;
161             if (startsWithASTORE03
162                     && asUnsignedByte(code[handled + 1]) == RETURN) {
163                 if (DEBUG) System.out.println("Drop 1");
164                 drops = true;
165                 afterHandler = handled + 1;
166             }
167             if (handled + 2 < code.length
168                     && opcode == ASTORE
169                     && asUnsignedByte(code[handled + 2]) == RETURN) {
170                 drops = true;
171                 afterHandler = handled + 2;
172                 if (DEBUG) System.out.println("Drop 2");
173             }
174             if (handled + 3 < code.length
175                     && !exitInTryBlock) {
176                 if (DEBUG) System.out.println("DE: checking for jumps");
177                 if (startsWithASTORE03
178                         && asUnsignedByte(code[handled - 3]) == GOTO) {
179                     int offsetBefore =
180                             getUnsignedShort(code, handled - 2);
181                     if (DEBUG) System.out.println("offset before = " + offsetBefore);
182                     if (offsetBefore == 4) {
183                         drops = true;
184                         afterHandler = handled + 1;
185                         if (DEBUG) System.out.println("Drop 3");
186                     }
187                 }
188                 if (opcode == ASTORE
189                         && asUnsignedByte(code[handled - 3]) == GOTO) {
190                     int offsetBefore =
191                             getUnsignedShort(code, handled - 2);
192                     if (offsetBefore == 5) {
193                         drops = true;
194                         afterHandler = handled + 2;
195                         if (DEBUG) System.out.println("Drop 4");
196                     }
197                 }
198                 if (startsWithASTORE03
199                         && asUnsignedByte(code[handled + 1]) == GOTO
200                         && asUnsignedByte(code[handled - 3]) == GOTO) {
201                     int offsetBefore =
202                             getUnsignedShort(code, handled - 2);
203                     int offsetAfter =
204                             getUnsignedShort(code, handled + 2);
205
206                     if (offsetAfter > 0 && offsetAfter + 4 == offsetBefore) {
207                         drops = true;
208                         afterHandler = handled + 4;
209                         if (DEBUG) System.out.println("Drop 5");
210                     }
211                 }
212
213                 if (opcode == ASTORE
214                         && asUnsignedByte(code[handled + 2]) == GOTO
215                         && asUnsignedByte(code[handled - 3]) == GOTO) {
216                     int offsetBefore =
217                             getUnsignedShort(code, handled - 2);
218                     int offsetAfter =
219                             getUnsignedShort(code, handled + 3);
220
221                     if (offsetAfter > 0 && offsetAfter + 5 == offsetBefore) {
222                         drops = true;
223                         afterHandler = handled + 5;
224                         if (DEBUG) System.out.println("Drop 6");
225                     }
226                 }
227
228             }
229
230             boolean multiLineHandler = false;
231             if (DEBUG)
232                 System.out.println("afterHandler = " + afterHandler
233                         + ", handled = " + handled);
234             if (afterHandler > handled && lineNumbers != null) {
235                 int startHandlerLinenumber = lineNumbers.getSourceLine(handled);
236
237                 int endHandlerLinenumber
238                         = getNextExecutableLineNumber(lineNumbers, afterHandler) - 1;
239                 if (DEBUG)
240                     System.out.println("Handler in lines "
241                             + startHandlerLinenumber
242                             + "-"
243                             + endHandlerLinenumber);
244                 if (endHandlerLinenumber > startHandlerLinenumber) {
245                     multiLineHandler = true;
246                     if (DEBUG) System.out.println("Multiline handler");
247                 }
248             }
249
250             if (end - start >= 5 && drops && !c.equals("java.lang.InterruptedException")
251                     && !c.equals("java.lang.CloneNotSupportedException")) {
252                 int priority = NORMAL_PRIORITY;
253                 if (exitInTryBlock) priority++;
254                 SourceLineAnnotation srcLine
255                         = SourceLineAnnotation.fromVisitedInstruction(this.classContext, this, handled);
256                 if (srcLine != null && LOOK_IN_SOURCE_TO_FIND_COMMENTED_CATCH_BLOCKS) {
257                     if (catchBlockHasComment(srcLine))
258                         return;
259                     else
260                         priority++;
261                 } else {
262                     // can't look at source
263
if (lineNumbers == null || multiLineHandler) priority += 2;
264                 }
265                 if (c.equals("java.lang.Error")
266                         || c.equals("java.lang.Exception")
267                         || c.equals("java.lang.Throwable")
268                         || c.equals("java.lang.RuntimeException")) {
269                     priority--;
270                     if (end - start > 30) priority--;
271                 }
272
273                 if (DEBUG) {
274                     System.out.println("Priority is " + priority);
275                 }
276                 if (priority > LOW_PRIORITY) return;
277                 if (priority < HIGH_PRIORITY) priority = HIGH_PRIORITY;
278                 if (DEBUG) {
279                     System.out.println("reporting warning");
280                 }
281
282                 String JavaDoc key = (exitInTryBlock ? "mightDrop," : "mightIgnore,") + getFullyQualifiedMethodName() + "," + c;
283                 if (reported.add(key)) {
284                     BugInstance bugInstance = new BugInstance(this, exitInTryBlock ? "DE_MIGHT_DROP" : "DE_MIGHT_IGNORE",
285                             priority)
286                             .addClassAndMethod(this);
287                     bugInstance.addClass(c).describe("CLASS_EXCEPTION");
288                     bugInstance.addSourceLine(srcLine);
289                     bugReporter.reportBug(bugInstance);
290                 }
291
292             }
293         }
294     }
295
296     private int getNextExecutableLineNumber(LineNumberTable linenumbers, int PC) {
297         LineNumber[] entries = linenumbers.getLineNumberTable();
298         int beforePC = 0;
299         int i = 0;
300         for (; i < entries.length && entries[i].getStartPC() < PC; i++) {
301             int line = entries[i].getLineNumber();
302             if (line > beforePC)
303                 beforePC = line;
304         }
305
306         if (i < entries.length) {
307             int secondChoice = entries[i].getLineNumber();
308             for (; i < entries.length; i++) {
309                 int line = entries[i].getLineNumber();
310                 if (line > beforePC)
311                     return line;
312             }
313             return secondChoice;
314         } else
315             return entries[entries.length - 1].getLineNumber();
316     }
317
318
319     private static final int START = 0;
320     private static final int CATCH = 1;
321     private static final int OPEN_PAREN = 2;
322     private static final int CLOSE_PAREN = 3;
323     private static final int OPEN_BRACE = 4;
324
325     /**
326      * Maximum number of lines we look backwards to find the
327      * "catch" keyword. Looking backwards is necessary
328      * when the indentation style puts the open brace on
329      * a different line from the catch clause.
330      */

331     private static final int NUM_CONTEXT_LINES = 3;
332
333     /**
334      * The number of lines that we'll scan to look at the source
335      * for a catch block.
336      */

337     private static final int MAX_LINES = 7;
338
339     /**
340      * Analyze a class's source code to see if there is a comment
341      * (or other text) in a catch block we have marked as dropping
342      * an exception.
343      *
344      * @return true if there is a comment in the catch block,
345      * false if not (or if we can't tell)
346      */

347     private boolean catchBlockHasComment(SourceLineAnnotation srcLine) {
348         if (!LOOK_IN_SOURCE_TO_FIND_COMMENTED_CATCH_BLOCKS)
349             return false;
350
351         SourceFinder sourceFinder = AnalysisContext.currentAnalysisContext().getSourceFinder();
352         try {
353             SourceFile sourceFile = sourceFinder.findSourceFile(srcLine.getPackageName(), srcLine.getSourceFile());
354             int startLine = srcLine.getStartLine();
355
356             int scanStartLine = startLine - NUM_CONTEXT_LINES;
357             if (scanStartLine < 1)
358                 scanStartLine = 1;
359
360             int offset = sourceFile.getLineOffset(scanStartLine - 1);
361             if (offset < 0)
362                 return false; // Source file has changed?
363
Tokenizer tokenizer = new Tokenizer(new InputStreamReader JavaDoc(sourceFile.getInputStreamFromOffset(offset)));
364
365             // Read the tokens into an ArrayList,
366
// keeping track of where the catch block is reported
367
// to start
368
ArrayList JavaDoc<Token> tokenList = new ArrayList JavaDoc<Token>(40);
369             int eolOfCatchBlockStart = -1;
370             for (int line = scanStartLine; line < scanStartLine + MAX_LINES;) {
371                 Token token = tokenizer.next();
372                 int kind = token.getKind();
373                 if (kind == Token.EOF)
374                     break;
375
376                 if (kind == Token.EOL) {
377                     if (line == startLine)
378                         eolOfCatchBlockStart = tokenList.size();
379                     ++line;
380                 }
381
382                 tokenList.add(token);
383             }
384
385             if (eolOfCatchBlockStart < 0)
386                 return false; // Couldn't scan line reported as start of catch block
387

388             // Starting at the end of the line reported as the start of the catch block,
389
// scan backwards for the token "catch".
390
ListIterator JavaDoc<Token> iter = tokenList.listIterator(eolOfCatchBlockStart);
391             boolean foundCatch = false;
392
393             while (iter.hasPrevious()) {
394                 Token token = iter.previous();
395                 if (token.getKind() == Token.WORD && token.getLexeme().equals("catch")) {
396                     foundCatch = true;
397                     break;
398                 }
399             }
400
401             if (!foundCatch)
402                 return false; // Couldn't find "catch" keyword
403

404             // Scan forward from the "catch" keyword to see what text
405
// is in the handler block. If the block is non-empty,
406
// then we suppress the warning (on the theory that the
407
// programmer has indicated that there is a good reason
408
// that the exception is ignored).
409
boolean done = false;
410             int numLines = 0;
411             int state = START;
412             int level = 0;
413             do {
414                 if (!iter.hasNext())
415                     break;
416
417                 Token token = iter.next();
418                 int type = token.getKind();
419                 String JavaDoc value = token.getLexeme();
420
421                 switch (type) {
422                 case Token.EOL:
423                     if (DEBUG) System.out.println("Saw token: [EOL]");
424                     ++numLines;
425                     if (numLines >= MAX_LINES)
426                         done = true;
427                     break;
428                 default:
429                     if (DEBUG) System.out.println("Got token: " + value);
430                     switch (state) {
431                     case START:
432                         if (value.equals("catch"))
433                             state = CATCH;
434                         break;
435                     case CATCH:
436                         if (value.equals("("))
437                             state = OPEN_PAREN;
438                         break;
439                     case OPEN_PAREN:
440                         if (value.equals(")")) {
441                             if (level == 0)
442                                 state = CLOSE_PAREN;
443                             else
444                                 --level;
445                         } else if (value.equals("(")) {
446                             ++level;
447                         }
448                         break;
449                     case CLOSE_PAREN:
450                         if (value.equals("{"))
451                             state = OPEN_BRACE;
452                         break;
453                     case OPEN_BRACE:
454                         boolean closeBrace = value.equals("}");
455                         if (DEBUG && !closeBrace) System.out.println("Found a comment in catch block: " + value);
456                         return !closeBrace;
457                     }
458                     break;
459                 }
460             } while (!done);
461         } catch (IOException JavaDoc e) {
462             // Ignored; we'll just assume there is no comment
463
if (DEBUG) e.printStackTrace();
464         }
465         return false;
466     }
467 }
468
469 // vim:ts=4
470
Popular Tags