edu.umd.cs.findbugs.detect.DroppedException Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* 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.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.ListIterator;
import java.util.Set;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.CodeException;
import org.apache.bcel.classfile.LineNumber;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.Utility;
import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.LocalVariableAnnotation;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.Token;
import edu.umd.cs.findbugs.Tokenizer;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.Hierarchy;
import edu.umd.cs.findbugs.ba.SourceFile;
import edu.umd.cs.findbugs.ba.SourceFinder;
import edu.umd.cs.findbugs.charsets.UTF8;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
public class DroppedException extends PreorderVisitor implements Detector {
private static final boolean DEBUG = SystemProperties.getBoolean("de.debug");
private static final boolean LOOK_IN_SOURCE_TO_FIND_COMMENTED_CATCH_BLOCKS = SystemProperties
.getBoolean("findbugs.de.comment");
Set causes = new HashSet<>();
Set checkedCauses = new HashSet<>();
private final BugReporter bugReporter;
private final BugAccumulator bugAccumulator;
private ClassContext classContext;
public DroppedException(BugReporter bugReporter) {
this.bugReporter = bugReporter;
this.bugAccumulator = new BugAccumulator(bugReporter);
if (DEBUG) {
System.out.println("Dropped Exception debugging turned on");
}
}
@Override
public void visitClassContext(ClassContext classContext) {
this.classContext = classContext;
classContext.getJavaClass().accept(this);
bugAccumulator.reportAccumulatedBugs();
}
@Override
public void report() {
}
boolean isChecked(String c) {
if (!causes.add(c)) {
return checkedCauses.contains(c);
}
try {
if (Hierarchy.isSubtype(c, "java.lang.Exception") && !Hierarchy.isSubtype(c, "java.lang.RuntimeException")) {
checkedCauses.add(c);
return true;
}
} catch (ClassNotFoundException e) {
bugReporter.reportMissingClass(e);
}
return false;
}
private int getUnsignedShort(byte[] a, int i) {
return asUnsignedByte(a[i]) << 8 | asUnsignedByte(a[i + 1]);
}
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "UC_USELESS_CONDITION",
justification = "To be fixed in SpotBugs 4.0.0, see https://github.com/spotbugs/spotbugs/issues/84")
@Override
public void visit(Code obj) {
CodeException[] exp = obj.getExceptionTable();
LineNumberTable lineNumbers = obj.getLineNumberTable();
if (exp == null) {
return;
}
byte[] code = obj.getCode();
for (CodeException aExp : exp) {
int handled = aExp.getHandlerPC();
int start = aExp.getStartPC();
int end = aExp.getEndPC();
int cause = aExp.getCatchType();
boolean exitInTryBlock = false;
if (DEBUG) {
System.out.println("start = " + start + ", end = " + end + ", codeLength = " + code.length + ", handled = "
+ handled);
}
for (int j = start; j <= end && j < code.length;) {
int opcode = asUnsignedByte(code[j]);
if (Const.getNoOfOperands(opcode) < 0) {
exitInTryBlock = true;
break;
}
j += 1 + Const.getNoOfOperands(opcode);
if (opcode >= Const.IRETURN && opcode <= Const.RETURN || opcode >= Const.IFEQ && opcode <= Const.GOTO && (opcode != Const.GOTO
|| j < end)) {
exitInTryBlock = true;
if (DEBUG) {
System.out.println("\texit: " + opcode + " in " + getFullyQualifiedMethodName());
}
break;
}
}
if (exitInTryBlock) {
if (DEBUG) {
System.out.println("Exit in try block");
}
continue;
}
if (handled < 5) {
continue;
}
@DottedClassName
String causeName;
if (cause == 0) {
causeName = "java.lang.Throwable";
} else {
causeName = Utility.compactClassName(getConstantPool().getConstantString(cause, Const.CONSTANT_Class), false);
if (!isChecked(causeName)) {
continue;
}
}
int jumpAtEnd = 0;
if (end < code.length && asUnsignedByte(code[end]) == Const.GOTO) {
jumpAtEnd = getUnsignedShort(code, end + 1);
if (jumpAtEnd < handled) {
jumpAtEnd = 0;
}
}
int opcode = asUnsignedByte(code[handled]);
int afterHandler = 0;
if (DEBUG) {
System.out.println("DE:\topcode is " + Const.getOpcodeName(opcode) + ", " + asUnsignedByte(code[handled + 1]));
}
boolean drops = false;
boolean startsWithASTORE03 = opcode >= Const.ASTORE_0 && opcode <= Const.ASTORE_3;
if (startsWithASTORE03 && asUnsignedByte(code[handled + 1]) == Const.RETURN) {
if (DEBUG) {
System.out.println("Drop 1");
}
drops = true;
afterHandler = handled + 1;
}
if (handled + 2 < code.length && opcode == Const.ASTORE && asUnsignedByte(code[handled + 2]) == Const.RETURN) {
drops = true;
afterHandler = handled + 2;
if (DEBUG) {
System.out.println("Drop 2");
}
}
if (handled + 3 < code.length && !exitInTryBlock) {
if (DEBUG) {
System.out.println("DE: checking for jumps");
}
if (startsWithASTORE03 && asUnsignedByte(code[handled - 3]) == Const.GOTO) {
int offsetBefore = getUnsignedShort(code, handled - 2);
if (DEBUG) {
System.out.println("offset before = " + offsetBefore);
}
if (offsetBefore == 4) {
drops = true;
afterHandler = handled + 1;
if (DEBUG) {
System.out.println("Drop 3");
}
}
}
if (opcode == Const.ASTORE && asUnsignedByte(code[handled - 3]) == Const.GOTO) {
int offsetBefore = getUnsignedShort(code, handled - 2);
if (offsetBefore == 5) {
drops = true;
afterHandler = handled + 2;
if (DEBUG) {
System.out.println("Drop 4");
}
}
}
if (startsWithASTORE03 && asUnsignedByte(code[handled + 1]) == Const.GOTO && asUnsignedByte(code[handled - 3]) == Const.GOTO) {
int offsetBefore = getUnsignedShort(code, handled - 2);
int offsetAfter = getUnsignedShort(code, handled + 2);
if (offsetAfter > 0 && offsetAfter + 4 == offsetBefore) {
drops = true;
afterHandler = handled + 4;
if (DEBUG) {
System.out.println("Drop 5");
}
}
}
if (opcode == Const.ASTORE && asUnsignedByte(code[handled + 2]) == Const.GOTO && asUnsignedByte(code[handled - 3]) == Const.GOTO) {
int offsetBefore = getUnsignedShort(code, handled - 2);
int offsetAfter = getUnsignedShort(code, handled + 3);
if (offsetAfter > 0 && offsetAfter + 5 == offsetBefore) {
drops = true;
afterHandler = handled + 5;
if (DEBUG) {
System.out.println("Drop 6");
}
}
}
}
boolean multiLineHandler = false;
if (DEBUG) {
System.out.println("afterHandler = " + afterHandler + ", handled = " + handled);
}
if (afterHandler > handled && lineNumbers != null) {
int startHandlerLinenumber = lineNumbers.getSourceLine(handled);
int endHandlerLinenumber = getNextExecutableLineNumber(lineNumbers, afterHandler) - 1;
if (DEBUG) {
System.out.println("Handler in lines " + startHandlerLinenumber + "-" + endHandlerLinenumber);
}
if (endHandlerLinenumber > startHandlerLinenumber) {
multiLineHandler = true;
if (DEBUG) {
System.out.println("Multiline handler");
}
}
}
if (end - start >= 4 && drops && !"java.lang.InterruptedException".equals(causeName)
&& !"java.lang.CloneNotSupportedException".equals(causeName)) {
int priority = NORMAL_PRIORITY;
if (exitInTryBlock) {
priority++;
}
if (end - start == 4) {
priority++;
}
SourceLineAnnotation srcLine = SourceLineAnnotation.fromVisitedInstruction(this.classContext, this, handled);
if (srcLine != null && LOOK_IN_SOURCE_TO_FIND_COMMENTED_CATCH_BLOCKS) {
if (catchBlockHasComment(srcLine)) {
return;
} else {
priority++;
}
} else {
// can't look at source
if (lineNumbers == null || multiLineHandler) {
priority += 2;
}
}
if ("java.lang.Error".equals(causeName) || "java.lang.Exception".equals(causeName) || "java.lang.Throwable".equals(causeName)
|| "java.lang.RuntimeException".equals(causeName)) {
priority--;
if (end - start > 30) {
priority--;
}
}
int register = -1;
if (startsWithASTORE03) {
register = opcode - Const.ASTORE_0;
} else if (opcode == Const.ASTORE) {
register = asUnsignedByte(code[handled + 1]);
}
if (register >= 0) {
LocalVariableAnnotation lva = LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), register, handled + 2, handled + 1);
String name = lva.getName();
if (DEBUG) {
System.out.println("Name: " + name);
}
if (name.startsWith("ignore") || name.startsWith("cant")) {
continue;
}
}
if (DEBUG) {
System.out.println("Priority is " + priority);
}
if (priority > LOW_PRIORITY) {
return;
}
if (priority < HIGH_PRIORITY) {
priority = HIGH_PRIORITY;
}
if (DEBUG) {
System.out.println("reporting warning");
}
BugInstance bugInstance = new BugInstance(this, exitInTryBlock ? "DE_MIGHT_DROP" : "DE_MIGHT_IGNORE", priority)
.addClassAndMethod(this);
bugInstance.addClass(causeName).describe("CLASS_EXCEPTION");
bugInstance.addSourceLine(srcLine);
bugAccumulator.accumulateBug(bugInstance, srcLine);
}
}
}
private int getNextExecutableLineNumber(LineNumberTable linenumbers, int PC) {
LineNumber[] entries = linenumbers.getLineNumberTable();
int beforePC = 0;
int i = 0;
for (; i < entries.length && entries[i].getStartPC() < PC; i++) {
int line = entries[i].getLineNumber();
if (line > beforePC) {
beforePC = line;
}
}
if (i < entries.length) {
int secondChoice = entries[i].getLineNumber();
for (; i < entries.length; i++) {
int line = entries[i].getLineNumber();
if (line > beforePC) {
return line;
}
}
return secondChoice;
} else {
return entries[entries.length - 1].getLineNumber();
}
}
private static final int START = 0;
private static final int CATCH = 1;
private static final int OPEN_PAREN = 2;
private static final int CLOSE_PAREN = 3;
private static final int OPEN_BRACE = 4;
/**
* Maximum number of lines we look backwards to find the "catch" keyword.
* Looking backwards is necessary when the indentation style puts the open
* brace on a different line from the catch clause.
*/
private static final int NUM_CONTEXT_LINES = 3;
/**
* The number of lines that we'll scan to look at the source for a catch
* block.
*/
private static final int MAX_LINES = 7;
/**
* Analyze a class's source code to see if there is a comment (or other
* text) in a catch block we have marked as dropping an exception.
*
* @return true if there is a comment in the catch block, false if not (or
* if we can't tell)
*/
private boolean catchBlockHasComment(SourceLineAnnotation srcLine) {
if (!LOOK_IN_SOURCE_TO_FIND_COMMENTED_CATCH_BLOCKS) {
return false;
}
SourceFinder sourceFinder = AnalysisContext.currentAnalysisContext().getSourceFinder();
try {
SourceFile sourceFile = sourceFinder.findSourceFile(srcLine.getPackageName(), srcLine.getSourceFile());
int startLine = srcLine.getStartLine();
int scanStartLine = startLine - NUM_CONTEXT_LINES;
if (scanStartLine < 1) {
scanStartLine = 1;
}
int offset = sourceFile.getLineOffset(scanStartLine - 1);
if (offset < 0) {
return false; // Source file has changed?
}
Tokenizer tokenizer = new Tokenizer(UTF8.reader(sourceFile.getInputStreamFromOffset(offset)));
// Read the tokens into an ArrayList,
// keeping track of where the catch block is reported
// to start
ArrayList tokenList = new ArrayList<>(40);
int eolOfCatchBlockStart = -1;
for (int line = scanStartLine; line < scanStartLine + MAX_LINES;) {
Token token = tokenizer.next();
int kind = token.getKind();
if (kind == Token.EOF) {
break;
}
if (kind == Token.EOL) {
if (line == startLine) {
eolOfCatchBlockStart = tokenList.size();
}
++line;
}
tokenList.add(token);
}
if (eolOfCatchBlockStart < 0) {
return false; // Couldn't scan line reported as start of catch
// block
}
// Starting at the end of the line reported as the start of the
// catch block,
// scan backwards for the token "catch".
ListIterator iter = tokenList.listIterator(eolOfCatchBlockStart);
boolean foundCatch = false;
while (iter.hasPrevious()) {
Token token = iter.previous();
if (token.getKind() == Token.WORD && "catch".equals(token.getLexeme())) {
foundCatch = true;
break;
}
}
if (!foundCatch) {
return false; // Couldn't find "catch" keyword
}
// Scan forward from the "catch" keyword to see what text
// is in the handler block. If the block is non-empty,
// then we suppress the warning (on the theory that the
// programmer has indicated that there is a good reason
// that the exception is ignored).
boolean done = false;
int numLines = 0;
int state = START;
int level = 0;
do {
if (!iter.hasNext()) {
break;
}
Token token = iter.next();
int type = token.getKind();
String value = token.getLexeme();
switch (type) {
case Token.EOL:
if (DEBUG) {
System.out.println("Saw token: [EOL]");
}
++numLines;
if (numLines >= MAX_LINES) {
done = true;
}
break;
default:
if (DEBUG) {
System.out.println("Got token: " + value);
}
switch (state) {
case START:
if ("catch".equals(value)) {
state = CATCH;
}
break;
case CATCH:
if ("(".equals(value)) {
state = OPEN_PAREN;
}
break;
case OPEN_PAREN:
if (")".equals(value)) {
if (level == 0) {
state = CLOSE_PAREN;
} else {
--level;
}
} else if ("(".equals(value)) {
++level;
}
break;
case CLOSE_PAREN:
if ("{".equals(value)) {
state = OPEN_BRACE;
}
break;
case OPEN_BRACE:
boolean closeBrace = "}".equals(value);
if (DEBUG && !closeBrace) {
System.out.println("Found a comment in catch block: " + value);
}
return !closeBrace;
}
break;
}
} while (!done);
} catch (IOException e) {
// Ignored; we'll just assume there is no comment
}
return false;
}
}