/* * 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; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import javax.annotation.CheckForNull; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.classfile.Global; import edu.umd.cs.findbugs.classfile.IAnalysisCache; import edu.umd.cs.findbugs.util.Util; /** * Bug rankers are used to compute a bug rank for each bug instance. Bug ranks 1-20 are for bugs that are visible to users. * Bug rank 1 is more the most relevant/scary bugs. A bug rank greater than 20 is for issues that should not be shown to users. * * * The following bug rankers may exist: * <ul> * <li> core bug ranker (loaded from etc/bugrank.txt) * <li> a bug ranker for each plugin (loaded from <plugin>/etc/bugrank.txt) * <li> A global adjustment ranker (loaded from plugins/adjustBugrank.txt) * </ul> * * A bug ranker is comprised of a list of bug patterns, bug kinds and bug categories. For each, either an absolute * or relative bug rank is provided. A relative rank is one preceeded by a + or -. * * For core bug detectors, the bug ranker search order is: * <ul> * <li> global bug ranker * <li> core bug ranker * </ul> * * For third party plugins, the bug ranker search order is: * <ul> * <li> global adjustment bug ranker * <li> plugin adjustment bug ranker * <li> core bug ranker * </ul> * * The overall search order is * <ul> * <li> Bug patterns, in search order across bug rankers * <li> Bug kinds, in search order across bug rankers * <li> Bug categories, in search order across bug rankers * </ul> * * Search stops at the first absolute bug rank found, and the result is the sum of all of relative bug ranks plus * the final absolute bug rank. Since all bug categories are defined by the core bug ranker, we should always find * an absolute bug rank. * * * * @author Bill Pugh */ public class BugRanker { static class Scorer { private final HashMap<String, Integer> adjustment = new HashMap<String, Integer>(); private final HashSet<String> isRelative = new HashSet<String>(); int get(String key) { Integer v = adjustment.get(key); if (v == null) return 0; return v; } boolean isRelative(String key) { return !adjustment.containsKey(key) || isRelative.contains(key); } void storeAdjustment(String key, String value) { int v = Integer.parseInt(value); char firstChar = value.charAt(0); adjustment.put(key, v); if (firstChar == '+' || firstChar == '-') isRelative.add(key); } } /** * @param u may be null. In this case, a default value will be used for all bugs * @throws IOException */ public BugRanker(@CheckForNull URL u) throws IOException { if(u == null){ return; } BufferedReader in = new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8")); while (true) { String s = in.readLine(); if (s == null) break; s = s.trim(); if (s.length() == 0) continue; String parts [] = s.split(" "); String rank = parts[0]; String kind = parts[1]; String what = parts[2]; if (kind.equals("BugPattern")) bugPatterns.storeAdjustment(what, rank); else if (kind.equals("BugKind")) bugKinds.storeAdjustment(what, rank); else if (kind.equals("Category")) bugCategories.storeAdjustment(what, rank); else AnalysisContext.logError("Can't parse bug rank " + s); } Util.closeSilently(in); } private final Scorer bugPatterns = new Scorer(); private final Scorer bugKinds = new Scorer(); private final Scorer bugCategories = new Scorer(); /** * */ public static final String FILENAME = "bugrank.txt"; public static final String ADJUST_FILENAME = "adjustBugrank.txt"; private static int priorityAdjustment(int priority) { switch (priority) { case Priorities.HIGH_PRIORITY: return 0; case Priorities.NORMAL_PRIORITY: return 2; case Priorities.LOW_PRIORITY: return 5; default: return 10; } } public static int rankBug(BugInstance bug, BugRanker... rankers) { return rankBugPatternWithPriorityAdustment(bug.getBugPattern(), bug.getPriority(), rankers); } private static int rankBugPatternWithPriorityAdustment(BugPattern bugPattern, int priority, BugRanker... rankers) { int rankBugPattern = rankBugPattern(bugPattern, rankers); int priorityAdjustment = priorityAdjustment(priority); if (rankBugPattern > 20) return rankBugPattern + priorityAdjustment; return Math.min(rankBugPattern + priorityAdjustment, 20); } private static int rankBugPattern(BugPattern bugPattern, BugRanker... rankers) { String type = bugPattern.getType(); int rank = 0; for(BugRanker b : rankers) if (b != null) { rank += b.bugPatterns.get(type); if (!b.bugPatterns.isRelative(type)) return rank; } String kind = bugPattern.getAbbrev(); for(BugRanker b : rankers) if (b != null) { rank += b.bugKinds.get(kind); if (!b.bugKinds.isRelative(kind)) return rank; } String category = bugPattern.getCategory(); for(BugRanker b : rankers) if (b != null) { rank += b.bugCategories.get(category); if (!b.bugCategories.isRelative(category)) return rank; } return 20; } private static BugRanker getAdjustmentBugRanker() { DetectorFactoryCollection factory = DetectorFactoryCollection.instance(); return factory.getAdjustmentBugRanker(); } private static BugRanker getCoreRanker() { DetectorFactoryCollection factory = DetectorFactoryCollection.instance(); return factory.getCorePlugin().getBugRanker(); } public static int findRank(BugInstance bug) { DetectorFactory detectorFactory = bug.getDetectorFactory(); if (null == detectorFactory) { // Unknown detector / plugin (e.g. happens when reading a bug // collection from its XML representation). return findRank(bug.getBugPattern(), bug.getPriority()); } Plugin plugin = detectorFactory.getPlugin(); BugRanker adjustmentRanker = getAdjustmentBugRanker(); BugRanker pluginRanker = plugin.getBugRanker(); BugRanker coreRanker = getCoreRanker(); if (pluginRanker == coreRanker) return rankBug(bug, adjustmentRanker, coreRanker); else return rankBug(bug, adjustmentRanker, pluginRanker, coreRanker); } public static int findRank(BugPattern pattern, Plugin plugin, int priority) { BugRanker adjustmentRanker = getAdjustmentBugRanker(); BugRanker pluginRanker = plugin.getBugRanker(); BugRanker coreRanker = getCoreRanker(); if (pluginRanker == coreRanker) return rankBugPatternWithPriorityAdustment(pattern, priority, adjustmentRanker, coreRanker); else return rankBugPatternWithPriorityAdustment(pattern, priority, adjustmentRanker, pluginRanker, coreRanker); } public static int findRank(BugPattern pattern, int priority) { DetectorFactoryCollection factory = DetectorFactoryCollection.instance(); Plugin corePlugin = factory.getCorePlugin(); List<BugRanker> rankers = new ArrayList<BugRanker>(); rankers.add(getAdjustmentBugRanker()); for (Plugin plugin : factory.plugins()) { if (plugin != corePlugin) { rankers.add(plugin.getBugRanker()); } } rankers.add(getCoreRanker()); return rankBugPatternWithPriorityAdustment(pattern, priority, rankers.toArray(new BugRanker[] {})); } public static void trimToMaxRank(BugCollection origCollection, int maxRank) { if (maxRank < 20) for(Iterator<BugInstance> i = origCollection.getCollection().iterator(); i.hasNext(); ) { BugInstance b = i.next(); if (BugRanker.findRank(b) > maxRank) i.remove(); } } }