/* * 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 javax.annotation.CheckForNull; import edu.umd.cs.findbugs.BugCollection; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugPattern; import edu.umd.cs.findbugs.BugRanker; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.ClassAnnotation; import edu.umd.cs.findbugs.Detector2; import edu.umd.cs.findbugs.DetectorFactory; import edu.umd.cs.findbugs.DetectorFactoryCollection; import edu.umd.cs.findbugs.FieldAnnotation; 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.Confidence; 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.XField; 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.FieldDescriptor; import edu.umd.cs.findbugs.classfile.FieldOrMethodDescriptor; 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 boolean initialized = false; private Map<ClassDescriptor, Collection<BugInstance>> warningsByClass; private Map<MethodDescriptor, Collection<BugInstance>> warningsByMethod; private Map<FieldDescriptor, Collection<BugInstance>> warningsByField; 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 (!initialized) { initialized = true; // // 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. // warningsByClass = new HashMap<ClassDescriptor, Collection<BugInstance>>(); warningsByMethod = new HashMap<MethodDescriptor, Collection<BugInstance>>(); warningsByField = new HashMap<FieldDescriptor, Collection<BugInstance>>(); for (Iterator<BugInstance> i = bugCollection.iterator(); i.hasNext();) { BugInstance warning = i.next(); MethodAnnotation method = warning.getPrimaryMethod(); if (method != null) { MethodDescriptor methodDesc = method.toMethodDescriptor(); Collection<BugInstance> warnings = warningsByMethod.get(methodDesc); if (warnings == null) { warnings = new LinkedList<BugInstance>(); warningsByMethod.put(methodDesc, warnings); } warnings.add(warning); } FieldAnnotation field = warning.getPrimaryField(); if (field != null) { if (DEBUG) System.out.println("primary field of " + field + " for " + warning); FieldDescriptor fieldDescriptor = field.toFieldDescriptor(); Collection<BugInstance> warnings = warningsByField.get(fieldDescriptor); if (warnings == null) { warnings = new LinkedList<BugInstance>(); warningsByField.put(fieldDescriptor, warnings); } warnings.add(warning); } if(field == null && method == null){ ClassAnnotation clazz = warning.getPrimaryClass(); if (clazz != null) { ClassDescriptor classDesc = clazz.getClassDescriptor(); Collection<BugInstance> warnings = warningsByClass.get(classDesc); if (warnings == null) { warnings = new LinkedList<BugInstance>(); warningsByClass.put(classDesc, 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(); if (DEBUG) { System.out.println("CEW: checking " + xclass.toString()); } check(xclass, expectWarning, true, HIGH_PRIORITY); check(xclass, desireWarning, true, NORMAL_PRIORITY); check(xclass, noWarning, false, HIGH_PRIORITY); check(xclass, desireNoWarning, false, NORMAL_PRIORITY); 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); } for (XField xfield : xclass.getXFields()) { if (DEBUG) { System.out.println("CEW: checking " + xfield.toString()); } check(xfield, expectWarning, true, HIGH_PRIORITY); check(xfield, desireWarning, true, NORMAL_PRIORITY); check(xfield, noWarning, false, HIGH_PRIORITY); check(xfield, desireNoWarning, false, NORMAL_PRIORITY); } } private void check(XClass xclass, ClassDescriptor annotation, boolean expectWarnings, int priority) { AnnotationValue expect = xclass.getAnnotation(annotation); if (expect == null) return; if (DEBUG) { System.out.println("*** Found " + annotation + " annotation on " + xclass); } ClassDescriptor descriptor = xclass.getClassDescriptor(); Collection<BugInstance> warnings = warningsByClass.get(descriptor); check(expect, descriptor, warnings, expectWarnings, priority, descriptor); } private void check(XMethod xmethod, ClassDescriptor annotation, boolean expectWarnings, int priority) { AnnotationValue expect = xmethod.getAnnotation(annotation); if (expect == null) return; if (DEBUG) { System.out.println("*** Found " + annotation + " annotation on " + xmethod); } FieldOrMethodDescriptor descriptor = xmethod.getMethodDescriptor(); Collection<BugInstance> warnings = warningsByMethod.get(descriptor); check(expect, descriptor, warnings, expectWarnings, priority, descriptor.getClassDescriptor()); } private void check(XField xfield, ClassDescriptor annotation, boolean expectWarnings, int priority) { AnnotationValue expect = xfield.getAnnotation(annotation); if (expect == null) return; if (DEBUG) { System.out.println("*** Found " + annotation + " annotation on " + xfield); } FieldOrMethodDescriptor descriptor = xfield.getFieldDescriptor(); Collection<BugInstance> warnings = warningsByField.get(descriptor); check(expect, descriptor, warnings, expectWarnings, priority, descriptor.getClassDescriptor()); } private void check(AnnotationValue expect, Object descriptor, Collection<BugInstance> warnings, boolean expectWarnings, int priority, ClassDescriptor cd) { if (expect != null) { String expectedBugCodes = (String) expect.getValue("value"); EnumValue wantedConfidence = (EnumValue) expect.getValue("confidence"); EnumValue wantedPriority = (EnumValue) expect.getValue("priority"); Integer num = (Integer) expect.getValue("num"); if (num == null) num = (expectWarnings ? 1 : 0); Integer rank = (Integer) expect.getValue("rank"); if (rank == null) rank = BugRanker.VISIBLE_RANK_MAX; int minPriority = Confidence.LOW.getConfidenceValue(); if (wantedConfidence != null) minPriority = Confidence.valueOf(wantedConfidence.value).getConfidenceValue(); else if (wantedPriority != null) minPriority = Priority.valueOf(wantedPriority.value).getPriorityValue(); if (DEBUG) { if (warnings == null) System.out.println("Checking " + expectedBugCodes + " against no bugs"); else { System.out.println("Checking " + expectedBugCodes + " against " + warnings.size() + " bugs"); for (BugInstance b : warnings) { System.out.println(" " + b.getType()); } } } if (expectedBugCodes == null || expectedBugCodes.trim().length() == 0) { checkAnnotation(null, warnings, expectWarnings, priority, rank, num, descriptor, minPriority, cd); } else { StringTokenizer tok = new StringTokenizer(expectedBugCodes, ","); while (tok.hasMoreTokens()) { String bugCode = tok.nextToken().trim(); if (!possibleBugCodes.contains(bugCode)) continue; checkAnnotation(bugCode, warnings, expectWarnings, priority, rank, num, descriptor, minPriority, cd); } } } } public void checkAnnotation(@CheckForNull String bugCode, Collection<BugInstance> warnings, boolean expectWarnings, int priority, Integer rank, Integer num, Object methodDescriptor, int minPriority, ClassDescriptor cd) { String bugCodeMessage = bugCode != null ? bugCode : "any bug"; Collection<SourceLineAnnotation> bugs = countWarnings(warnings, bugCode, minPriority, rank); if (expectWarnings && bugs.size() < num) { BugInstance bug = makeWarning("FB_MISSING_EXPECTED_WARNING", methodDescriptor, priority, cd).addString(bugCodeMessage); if (!bugs.isEmpty()) { bug.addString(String.format("Expected %d bugs, saw %d", num, bugs.size())); } reporter.reportBug(bug); } else if (bugs.size() > num) { BugInstance bug = makeWarning("FB_UNEXPECTED_WARNING", methodDescriptor, priority, cd).addString(bugCodeMessage); if (!expectWarnings) { for (SourceLineAnnotation s : bugs) { reporter.reportBug(bug.add(s)); } } else if(num > 1){ // num == 1 is default value. So if we set a non default value, and see more warnings // as expected, it's a problem bug.addString(String.format("Expected %d bugs, saw %d", num, bugs.size())); reporter.reportBug(bug); } } } /** * @param bugPattern * @param methodDescriptor * @param priority * @return */ public BugInstance makeWarning(String bugPattern, Object descriptor, int priority, ClassDescriptor cd) { BugInstance bug = new BugInstance(this, bugPattern, priority).addClass(cd); if (descriptor instanceof FieldDescriptor) bug.addField((FieldDescriptor)descriptor); else if (descriptor instanceof MethodDescriptor) bug.addMethod((MethodDescriptor)descriptor); else if (descriptor instanceof ClassDescriptor) bug.addClass((ClassDescriptor)descriptor); if (DEBUG) System.out.println("Reporting " + bug); return bug; } private static Collection<SourceLineAnnotation> countWarnings( Collection<BugInstance> warnings, @CheckForNull String bugCode, int desiredPriority, int rank) { 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() > desiredPriority) continue; if (warning.getBugRank() > rank) continue; if (bugCode == null) { matching.add(warning.getPrimarySourceLineAnnotation()); 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(); } }