/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2003-2008 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.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugPattern;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector2;
import edu.umd.cs.findbugs.DetectorFactory;
import edu.umd.cs.findbugs.DetectorFactoryCollection;
import edu.umd.cs.findbugs.MethodAnnotation;
import edu.umd.cs.findbugs.NonReportingDetector;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.annotations.DesireNoWarning;
import edu.umd.cs.findbugs.annotations.DesireWarning;
import edu.umd.cs.findbugs.annotations.ExpectWarning;
import edu.umd.cs.findbugs.annotations.NoWarning;
import edu.umd.cs.findbugs.annotations.Priority;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
import edu.umd.cs.findbugs.classfile.analysis.AnnotationValue;
import edu.umd.cs.findbugs.classfile.analysis.EnumValue;
import edu.umd.cs.findbugs.plan.AnalysisPass;
import edu.umd.cs.findbugs.plan.ExecutionPlan;
/**
* Check uses of the ExpectWarning and NoWarning annotations. This is for
* internal testing of FindBugs (against findbugsTestCases).
*
* @author David Hovemeyer
*/
public class CheckExpectedWarnings implements Detector2, NonReportingDetector {
private static final boolean DEBUG = SystemProperties.getBoolean("cew.debug");
private BugReporter reporter;
private final BugCollection bugCollection;
private Set<String> possibleBugCodes;
private Map<MethodDescriptor, Collection<BugInstance>> warningsByMethod;
private ClassDescriptor expectWarning;
private ClassDescriptor noWarning;
private ClassDescriptor desireWarning;
private ClassDescriptor desireNoWarning;
private boolean warned;
public CheckExpectedWarnings(BugReporter bugReporter) {
bugCollection = bugReporter.getBugCollection();
if (bugCollection != null) {
reporter = bugReporter;
expectWarning = DescriptorFactory.createClassDescriptor(ExpectWarning.class);
noWarning = DescriptorFactory.createClassDescriptor(NoWarning.class);
desireWarning = DescriptorFactory.createClassDescriptor(DesireWarning.class);
desireNoWarning = DescriptorFactory.createClassDescriptor(DesireNoWarning.class);
}
}
public void visitClass(ClassDescriptor classDescriptor) throws CheckedAnalysisException {
if (reporter == null) {
if (!warned) {
System.err
.println("*** NOTE ***: CheckExpectedWarnings disabled because bug reporter doesn't use a BugCollection");
warned = true;
}
return;
}
if (warningsByMethod == null) {
//
// Build index of all warnings reported so far, by method.
// Because this detector runs in a later pass than any
// reporting detector, all warnings should have been
// produced by this point.
//
warningsByMethod = new HashMap<MethodDescriptor, Collection<BugInstance>>();
for (Iterator<BugInstance> i = bugCollection.iterator(); i.hasNext();) {
BugInstance warning = i.next();
MethodAnnotation method = warning.getPrimaryMethod();
if (method != null) {
MethodDescriptor methodDesc = method.toXMethod().getMethodDescriptor();
Collection<BugInstance> warnings = warningsByMethod.get(methodDesc);
if (warnings == null) {
warnings = new LinkedList<BugInstance>();
warningsByMethod.put(methodDesc, warnings);
}
warnings.add(warning);
}
}
//
// Based on enabled detectors, figure out which bug codes
// could possibly be reported. Don't complain about
// expected warnings that would be produced by detectors
// that aren't enabled.
//
possibleBugCodes = new HashSet<String>();
ExecutionPlan executionPlan = Global.getAnalysisCache().getDatabase(ExecutionPlan.class);
Iterator<AnalysisPass> i = executionPlan.passIterator();
while (i.hasNext()) {
AnalysisPass pass = i.next();
Iterator<DetectorFactory> j = pass.iterator();
while (j.hasNext()) {
DetectorFactory factory = j.next();
Collection<BugPattern> reportedPatterns = factory.getReportedBugPatterns();
for (BugPattern pattern : reportedPatterns) {
possibleBugCodes.add(pattern.getType());
possibleBugCodes.add(pattern.getAbbrev());
}
}
}
if (DEBUG) {
System.out.println("CEW: possible warnings are " + possibleBugCodes);
}
}
XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, classDescriptor);
List<? extends XMethod> methods = xclass.getXMethods();
for (XMethod xmethod : methods) {
if (DEBUG) {
System.out.println("CEW: checking " + xmethod.toString());
}
check(xmethod, expectWarning, true, HIGH_PRIORITY);
check(xmethod, desireWarning, true, NORMAL_PRIORITY);
check(xmethod, noWarning, false, HIGH_PRIORITY);
check(xmethod, desireNoWarning, false, NORMAL_PRIORITY);
}
}
private void check(XMethod xmethod, ClassDescriptor annotation, boolean expectWarnings, int priority) {
AnnotationValue expect = xmethod.getAnnotation(annotation);
if (expect != null) {
if (DEBUG) {
System.out.println("*** Found " + annotation + " annotation");
}
String expectedBugCodes = (String) expect.getValue("value");
EnumValue wantedPriority = (EnumValue) expect.getValue("priority");
Integer num = (Integer) expect.getValue("num");
if (num == null)
num = 1;
Priority minPriority = Priority.LOW;
if (wantedPriority != null)
minPriority = Priority.valueOf(wantedPriority.value);
StringTokenizer tok = new StringTokenizer(expectedBugCodes, ",");
while (tok.hasMoreTokens()) {
String bugCode = tok.nextToken();
if (!possibleBugCodes.contains(bugCode))
continue;
Collection<SourceLineAnnotation> bugs = countWarnings(xmethod.getMethodDescriptor(), bugCode, minPriority);
if (expectWarnings && bugs.size() < num) {
BugInstance bug = new BugInstance(this, "FB_MISSING_EXPECTED_WARNING", priority).addClassAndMethod(
xmethod.getMethodDescriptor()).addString(bugCode);
if (!bugs.isEmpty()) {
bug.addString(String.format("Expected %d bugs, saw %d", num, bugs.size()));
}
reporter.reportBug(bug);
} else if (!expectWarnings && !bugs.isEmpty() )
for (SourceLineAnnotation s : bugs) {
reporter.reportBug(new BugInstance(this, "FB_UNEXPECTED_WARNING", priority)
.addClassAndMethod(xmethod.getMethodDescriptor()).addString(bugCode).add(s));
}
}
}
}
private Collection<SourceLineAnnotation> countWarnings(MethodDescriptor methodDescriptor, String bugCode, Priority minPriority) {
Collection<BugInstance> warnings = warningsByMethod.get(methodDescriptor);
Collection<SourceLineAnnotation> matching = new HashSet<SourceLineAnnotation>();
DetectorFactoryCollection i18n = DetectorFactoryCollection.instance();
boolean matchPattern = false;
try {
i18n.getBugCode(bugCode);
} catch (IllegalArgumentException e) {
matchPattern = true;
}
if (warnings != null) {
for (BugInstance warning : warnings) {
if (warning.getPriority() > minPriority.getPriorityValue())
continue;
BugPattern pattern = warning.getBugPattern();
String match;
if (matchPattern)
match = pattern.getType();
else
match = pattern.getAbbrev();
if (match.equals(bugCode)) {
matching.add(warning.getPrimarySourceLineAnnotation());
}
}
}
return matching;
}
public void finishPass() {
HashSet<BugPattern> claimedReported = new HashSet<BugPattern>();
for (DetectorFactory d : DetectorFactoryCollection.instance().getFactories())
claimedReported.addAll(d.getReportedBugPatterns());
for (BugPattern b : DetectorFactoryCollection.instance().getBugPatterns()) {
String category = b.getCategory();
if (!b.isDeprecated() && !category.equals("EXPERIMENTAL") && !claimedReported.contains(b))
AnalysisContext.logError("No detector claims " + b.getType());
}
}
public String getDetectorClassName() {
return CheckExpectedWarnings.class.getName();
}
}