/* * FindBugs - Find Bugs in Java programs * Copyright (C) 2006-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.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.annotation.CheckForNull; import org.apache.bcel.classfile.ClassFormatException; import org.dom4j.DocumentException; import edu.umd.cs.findbugs.asm.FBClassReader; import edu.umd.cs.findbugs.ba.AnalysisCacheToAnalysisContextAdapter; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.AnalysisFeatures; import edu.umd.cs.findbugs.ba.ObjectTypeFactory; import edu.umd.cs.findbugs.ba.SourceInfoMap; import edu.umd.cs.findbugs.ba.XClass; import edu.umd.cs.findbugs.ba.XFactory; import edu.umd.cs.findbugs.ba.XField; import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierAnnotation; import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierApplications; import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierValue; 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.IAnalysisCache; import edu.umd.cs.findbugs.classfile.IAnalysisEngineRegistrar; import edu.umd.cs.findbugs.classfile.IClassFactory; import edu.umd.cs.findbugs.classfile.IClassObserver; import edu.umd.cs.findbugs.classfile.IClassPath; import edu.umd.cs.findbugs.classfile.IClassPathBuilder; import edu.umd.cs.findbugs.classfile.ICodeBase; import edu.umd.cs.findbugs.classfile.MissingClassException; import edu.umd.cs.findbugs.classfile.analysis.ClassData; import edu.umd.cs.findbugs.classfile.analysis.ClassNameAndSuperclassInfo; import edu.umd.cs.findbugs.classfile.impl.ClassFactory; import edu.umd.cs.findbugs.config.AnalysisFeatureSetting; import edu.umd.cs.findbugs.config.UserPreferences; import edu.umd.cs.findbugs.filter.FilterException; import edu.umd.cs.findbugs.log.Profiler; import edu.umd.cs.findbugs.log.YourKitController; import edu.umd.cs.findbugs.plan.AnalysisPass; import edu.umd.cs.findbugs.plan.ExecutionPlan; import edu.umd.cs.findbugs.plan.OrderingConstraintException; import edu.umd.cs.findbugs.util.ClassName; import edu.umd.cs.findbugs.util.TopologicalSort.OutEdges; /** * FindBugs driver class. * Orchestrates the analysis of a project, collection of results, etc. * * @author David Hovemeyer */ public class FindBugs2 implements IFindBugsEngine2 { private static final boolean LIST_ORDER = SystemProperties.getBoolean("findbugs.listOrder"); private static final boolean VERBOSE = SystemProperties.getBoolean("findbugs.verbose"); public static final boolean DEBUG = VERBOSE || SystemProperties.getBoolean("findbugs.debug"); public static final boolean PROGRESS = DEBUG || SystemProperties.getBoolean("findbugs.progress"); private static final boolean SCREEN_FIRST_PASS_CLASSES = SystemProperties.getBoolean("findbugs.screenFirstPass"); private static final boolean DEBUG_UA = SystemProperties.getBoolean("ua.debug"); private int rankThreshold; private List<IClassObserver> classObserverList; private ErrorCountingBugReporter bugReporter; private Project project; private IClassFactory classFactory; private IClassPath classPath; private IAnalysisCache analysisCache; private List<ClassDescriptor> appClassList; private Collection<ClassDescriptor> referencedClassSet; private DetectorFactoryCollection detectorFactoryCollection; private ExecutionPlan executionPlan; private YourKitController yourkitController = new YourKitController(); private String currentClassName; private FindBugsProgress progress; private IClassScreener classScreener; private AnalysisOptions analysisOptions = new AnalysisOptions(true); /** * Constructor. */ public FindBugs2() { this.classObserverList = new LinkedList<IClassObserver>(); this.analysisOptions.analysisFeatureSettingList = FindBugs.DEFAULT_EFFORT; this.progress = new NoOpFindBugsProgress(); // By default, do not exclude any classes via the class screener this.classScreener = new IClassScreener() { /* (non-Javadoc) * @see edu.umd.cs.findbugs.IClassScreener#matches(java.lang.String) */ public boolean matches(String fileName) { return true; } public boolean vacuous() { return true; } }; // By default, we do not want to scan nested archives this.analysisOptions.scanNestedArchives = false; // bug 2815983: no bugs are reported anymore // there is no info which value should be default, so using the any one rankThreshold = 42; } /** * Set the detector factory collection to be used by this * FindBugs2 engine. This method should be called before * the execute() method is called. * * @param detectorFactoryCollection The detectorFactoryCollection to set. */ public void setDetectorFactoryCollection( DetectorFactoryCollection detectorFactoryCollection) { this.detectorFactoryCollection = detectorFactoryCollection; } /** * Execute the analysis. * For obscure reasons, CheckedAnalysisExceptions are re-thrown * as IOExceptions. However, these can only happen during the * setup phase where we scan codebases for classes. * * @throws IOException * @throws InterruptedException */ public void execute() throws IOException, InterruptedException { if (FindBugs.noAnalysis) throw new UnsupportedOperationException("This FindBugs invocation was started without analysis capabilities"); Profiler profiler = bugReporter.getProjectStats().getProfiler(); try { // Get the class factory for creating classpath/codebase/etc. classFactory = ClassFactory.instance(); // The class path object createClassPath(); // The analysis cache object createAnalysisCache(); progress.reportNumberOfArchives(project.getFileCount() + project.getNumAuxClasspathEntries()); profiler.start(this.getClass()); // Discover all codebases in classpath and // enumerate all classes (application and non-application) buildClassPath(); // Build set of classes referenced by application classes buildReferencedClassSet(); // Create BCEL compatibility layer createAnalysisContext(project, appClassList, analysisOptions.sourceInfoFileName); // Configure the BugCollection (if we are generating one) FindBugs.configureBugCollection(this); // Enable/disabled relaxed reporting mode FindBugsAnalysisFeatures.setRelaxedMode(analysisOptions.relaxedReportingMode); FindBugsDisplayFeatures.setAbridgedMessages(analysisOptions.abridgedMessages); // Configure training databases FindBugs.configureTrainingDatabases(this); // Configure analysis features configureAnalysisFeatures(); // Create the execution plan (which passes/detectors to execute) createExecutionPlan(); if (!classScreener.vacuous()) { final BugReporter origBugReporter = bugReporter.getDelegate(); BugReporter filterBugReporter = new DelegatingBugReporter(origBugReporter) { @Override public void reportBug(BugInstance bugInstance) { String className = bugInstance.getPrimaryClass().getClassName(); String resourceName = className.replace('.', '/') + ".class"; if (classScreener.matches(resourceName)) { this.getDelegate().reportBug(bugInstance); } } }; bugReporter.setDelegate(filterBugReporter); } if (appClassList.size() == 0) { if (analysisOptions.noClassOk) { System.err.println("No classfiles specified; output will have no warnings"); } else { throw new NoClassesFoundToAnalyzeException(classPath); } } // Analyze the application analyzeApplication(); } catch (CheckedAnalysisException e) { IOException ioe = new IOException("IOException while scanning codebases"); ioe.initCause(e); throw ioe; } catch (OutOfMemoryError e) { System.err.println("Out of memory"); System.err.println("Total memory: " + Runtime.getRuntime().maxMemory() / 1000000 + "M"); System.err.println(" free memory: " + Runtime.getRuntime().freeMemory() / 1000000 + "M"); for (String s : project.getFileList()) { System.err.println("Analyzed: " + s); } for (String s : project.getAuxClasspathEntryList()) { System.err.println(" Aux: " + s); } throw e; } finally { AnalysisContext.removeCurrentAnalysisContext(); Global.removeAnalysisCacheForCurrentThread(); DescriptorFactory.clearInstance(); ObjectTypeFactory.clearInstance(); TypeQualifierApplications.clearInstance(); TypeQualifierAnnotation.clearInstance(); TypeQualifierValue.clearInstance(); // Make sure the codebases on the classpath are closed if (classPath != null) { classPath.close(); } profiler.end(this.getClass()); profiler.report(); } } /** * To avoid cyclic cross-references and allow GC after engine is not more needed. * (used by Eclipse plugin) */ public void dispose() { if (executionPlan != null) executionPlan.dispose(); if (appClassList != null) appClassList.clear(); if (classObserverList != null) classObserverList.clear(); if (referencedClassSet != null) referencedClassSet.clear(); analysisCache = null; analysisOptions.analysisFeatureSettingList = null; bugReporter = null; classFactory = null; classPath = null; classScreener = null; detectorFactoryCollection = null; executionPlan = null; progress = null; project = null; analysisOptions.userPreferences = null; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getBugReporter() */ public BugReporter getBugReporter() { return bugReporter; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getProject() */ public Project getProject() { return project; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#addClassObserver(edu.umd.cs.findbugs.classfile.IClassObserver) */ public void addClassObserver(IClassObserver classObserver) { classObserverList.add(classObserver); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#addFilter(java.lang.String, boolean) */ public void addFilter(String filterFileName, boolean include) throws IOException, FilterException { FindBugs.configureFilter(bugReporter, filterFileName, include); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#addBaselineBugs(java.lang.String) */ public void excludeBaselineBugs(String baselineBugs) throws IOException, DocumentException { FindBugs.configureBaselineFilter(bugReporter, baselineBugs); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#enableTrainingInput(java.lang.String) */ public void enableTrainingInput(String trainingInputDir) { this.analysisOptions.trainingInputDir = trainingInputDir; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#enableTrainingOutput(java.lang.String) */ public void enableTrainingOutput(String trainingOutputDir) { this.analysisOptions.trainingOutputDir = trainingOutputDir; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getBugCount() */ public int getBugCount() { return bugReporter.getBugCount(); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getCurrentClass() */ public String getCurrentClass() { return currentClassName; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getErrorCount() */ public int getErrorCount() { return bugReporter.getErrorCount(); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getMissingClassCount() */ public int getMissingClassCount() { return bugReporter.getMissingClassCount(); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getReleaseName() */ public String getReleaseName() { return analysisOptions.releaseName; } public String getProjectName() { return analysisOptions.projectName; } public void setProjectName(String name) { analysisOptions.projectName = name; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setAnalysisFeatureSettings(edu.umd.cs.findbugs.config.AnalysisFeatureSetting[]) */ public void setAnalysisFeatureSettings(AnalysisFeatureSetting[] settingList) { this.analysisOptions.analysisFeatureSettingList = settingList; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setBugReporter(edu.umd.cs.findbugs.BugReporter) */ public void setBugReporter(BugReporter bugReporter) { this.bugReporter = new ErrorCountingBugReporter(bugReporter); addClassObserver(bugReporter); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setClassScreener(edu.umd.cs.findbugs.ClassScreener) */ public void setClassScreener(IClassScreener classScreener) { this.classScreener = classScreener; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setProgressCallback(edu.umd.cs.findbugs.FindBugsProgress) */ public void setProgressCallback(FindBugsProgress progressCallback) { this.progress = progressCallback; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setProject(edu.umd.cs.findbugs.Project) */ public void setProject(Project project) { this.project = project; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setRelaxedReportingMode(boolean) */ public void setRelaxedReportingMode(boolean relaxedReportingMode) { this.analysisOptions.relaxedReportingMode = relaxedReportingMode; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setReleaseName(java.lang.String) */ public void setReleaseName(String releaseName) { this.analysisOptions.releaseName = releaseName; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setSourceInfoFile(java.lang.String) */ public void setSourceInfoFile(String sourceInfoFile) { this.analysisOptions.sourceInfoFileName = sourceInfoFile; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setUserPreferences(edu.umd.cs.findbugs.config.UserPreferences) */ public void setUserPreferences(UserPreferences userPreferences) { this.analysisOptions.userPreferences = userPreferences; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#emitTrainingOutput() */ public boolean emitTrainingOutput() { return analysisOptions.trainingOutputDir != null; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getUserPreferences() */ public UserPreferences getUserPreferences() { return analysisOptions.userPreferences; } /** * Create the classpath object. */ private void createClassPath() { classPath = classFactory.createClassPath(); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getTrainingInputDir() */ public String getTrainingInputDir() { return analysisOptions.trainingInputDir; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#getTrainingOutputDir() */ public String getTrainingOutputDir() { return analysisOptions.trainingOutputDir; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#useTrainingInput() */ public boolean useTrainingInput() { return analysisOptions.trainingInputDir != null; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setScanNestedArchives(boolean) */ public void setScanNestedArchives(boolean scanNestedArchives) { this.analysisOptions.scanNestedArchives = scanNestedArchives; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setNoClassOk(boolean) */ public void setNoClassOk(boolean noClassOk) { this.analysisOptions.noClassOk = noClassOk; } /** * Create the analysis cache object. * * @throws IOException if error occurs registering analysis engines in a plugin */ private void createAnalysisCache() throws IOException { analysisCache = ClassFactory.instance().createAnalysisCache(classPath, bugReporter); // Register the "built-in" analysis engines registerBuiltInAnalysisEngines(analysisCache); // Register analysis engines in plugins registerPluginAnalysisEngines(detectorFactoryCollection, analysisCache); // Install the DetectorFactoryCollection as a database analysisCache.eagerlyPutDatabase(DetectorFactoryCollection.class, detectorFactoryCollection); Global.setAnalysisCacheForCurrentThread(analysisCache); } /** * Register the "built-in" analysis engines with given IAnalysisCache. * * @param analysisCache an IAnalysisCache */ public static void registerBuiltInAnalysisEngines(IAnalysisCache analysisCache) { new edu.umd.cs.findbugs.classfile.engine.EngineRegistrar().registerAnalysisEngines(analysisCache); new edu.umd.cs.findbugs.classfile.engine.asm.EngineRegistrar().registerAnalysisEngines(analysisCache); new edu.umd.cs.findbugs.classfile.engine.bcel.EngineRegistrar().registerAnalysisEngines(analysisCache); } /** * Register all of the analysis engines defined in the plugins * contained in a DetectorFactoryCollection with an IAnalysisCache. * * @param detectorFactoryCollection a DetectorFactoryCollection * @param analysisCache an IAnalysisCache * @throws IOException */ public static void registerPluginAnalysisEngines( DetectorFactoryCollection detectorFactoryCollection, IAnalysisCache analysisCache) throws IOException { for (Iterator<Plugin> i = detectorFactoryCollection.pluginIterator(); i.hasNext(); ) { Plugin plugin = i.next(); Class<? extends IAnalysisEngineRegistrar> engineRegistrarClass = plugin.getEngineRegistrarClass(); if (engineRegistrarClass != null) { try { IAnalysisEngineRegistrar engineRegistrar = engineRegistrarClass.newInstance(); engineRegistrar.registerAnalysisEngines(analysisCache); } catch (InstantiationException e) { IOException ioe = new IOException( "Could not create analysis engine registrar for plugin " + plugin.getPluginId()); ioe.initCause(e); throw ioe; } catch (IllegalAccessException e) { IOException ioe = new IOException( "Could not create analysis engine registrar for plugin " + plugin.getPluginId()); ioe.initCause(e); throw ioe; } } } } /** * Build the classpath from project codebases and system codebases. * * @throws InterruptedException if the analysis thread is interrupted * @throws IOException if an I/O error occurs * @throws CheckedAnalysisException */ private void buildClassPath() throws InterruptedException, IOException, CheckedAnalysisException { IClassPathBuilder builder = classFactory.createClassPathBuilder(bugReporter); for (String path : project.getFileArray()) { builder.addCodeBase(classFactory.createFilesystemCodeBaseLocator(path), true); } for (String path : project.getAuxClasspathEntryList()) { builder.addCodeBase(classFactory.createFilesystemCodeBaseLocator(path), false); } builder.scanNestedArchives(analysisOptions.scanNestedArchives); builder.build(classPath, progress); appClassList = builder.getAppClassList(); if (PROGRESS) { System.out.println(appClassList.size() + " classes scanned"); } // If any of the application codebases contain source code, // add them to the source path. // Also, use the last modified time of application codebases // to set the project timestamp. for (Iterator<? extends ICodeBase> i = classPath.appCodeBaseIterator(); i.hasNext(); ){ ICodeBase appCodeBase = i.next(); if (appCodeBase.containsSourceFiles()) { String pathName = appCodeBase.getPathName(); if (pathName != null) { project.addSourceDir(pathName); } } project.addTimestamp(appCodeBase.getLastModifiedTime()); } } private void buildReferencedClassSet() throws CheckedAnalysisException, InterruptedException { // XXX: should drive progress dialog (scanning phase)? if (PROGRESS) { System.out.println("Adding referenced classes"); } Set<String> referencedPackageSet = new HashSet<String>(); LinkedList<ClassDescriptor> workList = new LinkedList<ClassDescriptor>(); workList.addAll(appClassList); Set<ClassDescriptor> seen = new HashSet<ClassDescriptor>(); Set<ClassDescriptor> appClassSet = new HashSet<ClassDescriptor>(appClassList); Set<ClassDescriptor> badAppClassSet = new HashSet<ClassDescriptor>(); HashSet<ClassDescriptor> knownDescriptors = new HashSet<ClassDescriptor>(DescriptorFactory.instance().getAllClassDescriptors()); int count = 0; Set<ClassDescriptor> addedToWorkList = new HashSet<ClassDescriptor>(appClassList); // add fields if (false) for(ClassDescriptor classDesc : appClassList) { try { XClass classNameAndInfo = Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc); for(XField f : classNameAndInfo.getXFields()) { String sig = f.getSignature(); ClassDescriptor d = DescriptorFactory.createClassDescriptorFromFieldSignature(sig); if (d != null && addedToWorkList.add(d)) workList.addLast(d); } } catch (RuntimeException e) { bugReporter.logError("Error scanning " + classDesc + " for referenced classes", e); if (appClassSet.contains(classDesc)) { badAppClassSet.add(classDesc); } } catch (MissingClassException e) { // Just log it as a missing class bugReporter.reportMissingClass(e.getClassDescriptor()); if (appClassSet.contains(classDesc)) { badAppClassSet.add(classDesc); } } } while (!workList.isEmpty()) { if (Thread.interrupted()) { throw new InterruptedException(); } ClassDescriptor classDesc = workList.removeFirst(); if (seen.contains(classDesc)) { continue; } seen.add(classDesc); if (!knownDescriptors.contains(classDesc)) { count++; if (PROGRESS && count % 5000 == 0) { System.out.println("Adding referenced class " + classDesc); } } referencedPackageSet.add(classDesc.getPackageName()); // Get list of referenced classes and add them to set. // Add superclasses and superinterfaces to worklist. try { XClass classNameAndInfo = Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc); ClassDescriptor superclassDescriptor = classNameAndInfo.getSuperclassDescriptor(); if (superclassDescriptor != null && addedToWorkList.add(superclassDescriptor)) { workList.addLast(superclassDescriptor); } for (ClassDescriptor ifaceDesc : classNameAndInfo.getInterfaceDescriptorList()) { if (addedToWorkList.add(ifaceDesc)) workList.addLast(ifaceDesc); } } catch (RuntimeException e) { bugReporter.logError("Error scanning " + classDesc + " for referenced classes", e); if (appClassSet.contains(classDesc)) { badAppClassSet.add(classDesc); } } catch (MissingClassException e) { // Just log it as a missing class bugReporter.reportMissingClass(e.getClassDescriptor()); if (appClassSet.contains(classDesc)) { badAppClassSet.add(classDesc); } } catch (CheckedAnalysisException e) { // Failed to scan a referenced class --- just log the error and continue bugReporter.logError("Error scanning " + classDesc + " for referenced classes", e); if (appClassSet.contains(classDesc)) { badAppClassSet.add(classDesc); } } } // Delete any application classes that could not be read appClassList.removeAll(badAppClassSet); DescriptorFactory.instance().purge(badAppClassSet); for(ClassDescriptor d : DescriptorFactory.instance().getAllClassDescriptors()) { referencedPackageSet.add(d.getPackageName()); } // Based on referenced packages, add any resolvable package-info classes // to the set of referenced classes. if (PROGRESS) { referencedPackageSet.remove(""); System.out.println("Added " + count + " referenced classes"); System.out.println("Total of " + referencedPackageSet.size() + " packages"); } // TODO the block below seems to be an old workaround which does not add any value // except even more "package-info not found" exceptions if(Boolean.getBoolean("fb.addPackageInfo")) for (String pkg : referencedPackageSet) { ClassDescriptor pkgInfoDesc = DescriptorFactory.instance().getClassDescriptorForDottedClassName(pkg + ".package-info"); if (DEBUG) { System.out.println("Checking package " + pkg + " for package-info..."); } try { analysisCache.getClassAnalysis(ClassData.class, pkgInfoDesc); // check that data is there analysisCache.getClassAnalysis(XClass.class, pkgInfoDesc); if (DEBUG) { System.out.println(" Adding " + pkgInfoDesc + " to referenced classes"); } } catch (CheckedAnalysisException e) { // Ignore } } referencedClassSet = new ArrayList<ClassDescriptor>(DescriptorFactory.instance().getAllClassDescriptors()); } public List<ClassDescriptor> sortByCallGraph(Collection<ClassDescriptor> classList, OutEdges<ClassDescriptor> outEdges) { List<ClassDescriptor> evaluationOrder = edu.umd.cs.findbugs.util.TopologicalSort.sortByCallGraph(classList, outEdges); edu.umd.cs.findbugs.util.TopologicalSort.countBadEdges(evaluationOrder, outEdges); return evaluationOrder; } public static void clearAnalysisContext() { AnalysisContext.removeCurrentAnalysisContext(); } /** * Create the AnalysisContext that will serve as the BCEL-compatibility * layer over the AnalysisCache. * @param project The project * @param appClassList list of ClassDescriptors identifying application classes * @param sourceInfoFileName name of source info file (null if none) */ public static void createAnalysisContext( Project project, List<ClassDescriptor> appClassList, @CheckForNull String sourceInfoFileName) throws CheckedAnalysisException, IOException { AnalysisCacheToAnalysisContextAdapter analysisContext = new AnalysisCacheToAnalysisContextAdapter(); // Make this the current analysis context AnalysisContext.setCurrentAnalysisContext(analysisContext); // Make the AnalysisCache the backing store for // the BCEL Repository analysisContext.clearRepository(); // Specify which classes are application classes analysisContext.setAppClassList(appClassList); // If needed, load SourceInfoMap if (sourceInfoFileName != null) { SourceInfoMap sourceInfoMap = analysisContext.getSourceInfoMap(); sourceInfoMap.read(new FileInputStream(sourceInfoFileName)); } analysisContext.setProject(project); } /** * Configure analysis feature settings. */ private void configureAnalysisFeatures() { for (AnalysisFeatureSetting setting : analysisOptions.analysisFeatureSettingList) { setting.configure(AnalysisContext.currentAnalysisContext()); } AnalysisContext.currentAnalysisContext().setBoolProperty(AnalysisFeatures.MERGE_SIMILAR_WARNINGS, analysisOptions.mergeSimilarWarnings); } /** * Create an execution plan. * * @throws OrderingConstraintException if the detector ordering constraints are inconsistent */ private void createExecutionPlan() throws OrderingConstraintException { executionPlan = new ExecutionPlan(); // Use user preferences to decide which detectors are enabled. DetectorFactoryChooser detectorFactoryChooser = new DetectorFactoryChooser() { HashSet<DetectorFactory> forcedEnabled = new HashSet<DetectorFactory>(); /* (non-Javadoc) * @see edu.umd.cs.findbugs.DetectorFactoryChooser#choose(edu.umd.cs.findbugs.DetectorFactory) */ public boolean choose(DetectorFactory factory) { return FindBugs.isDetectorEnabled(FindBugs2.this, factory, rankThreshold) || forcedEnabled.contains(factory); } public void enable(DetectorFactory factory) { forcedEnabled.add(factory); factory.setEnabledButNonReporting(true); } }; executionPlan.setDetectorFactoryChooser(detectorFactoryChooser); // Add plugins for (Iterator<Plugin> i = detectorFactoryCollection.pluginIterator(); i.hasNext(); ) { Plugin plugin = i.next(); if (DEBUG) { System.out.println("Adding plugin " + plugin.getPluginId() + " to execution plan"); } executionPlan.addPlugin(plugin); } // Build the execution plan executionPlan.build(); // Stash the ExecutionPlan in the AnalysisCache. Global.getAnalysisCache().eagerlyPutDatabase(ExecutionPlan.class, executionPlan); if (PROGRESS) { System.out.println(executionPlan.getNumPasses() + " passes in execution plan"); } } static private final boolean USE_REFERENCES = SystemProperties.getBoolean("tsort.references"); /** * Analyze the classes in the application codebase. */ private void analyzeApplication() throws InterruptedException { int passCount = 0; Profiler profiler = bugReporter.getProjectStats().getProfiler(); profiler.start(this.getClass()); AnalysisContext.currentXFactory().canonicalizeAll(); try { boolean multiplePasses = executionPlan.getNumPasses() > 1; if (executionPlan.getNumPasses() == 0) { throw new AssertionError("no analysis passes"); } int[] classesPerPass = new int[executionPlan.getNumPasses()]; classesPerPass[0] = referencedClassSet.size(); for (int i = 0; i < classesPerPass.length; i++) { classesPerPass[i] = i == 0 ? referencedClassSet.size() : appClassList.size(); } progress.predictPassCount(classesPerPass); XFactory factory = AnalysisContext.currentXFactory(); Collection<ClassDescriptor> badClasses = new LinkedList<ClassDescriptor>(); for (ClassDescriptor desc : referencedClassSet) { try { XClass info = Global.getAnalysisCache().getClassAnalysis(XClass.class, desc); factory.intern(info); } catch (CheckedAnalysisException e) { AnalysisContext.logError("Couldn't get class info for " + desc, e); badClasses.add(desc); } catch (RuntimeException e) { AnalysisContext.logError("Couldn't get class info for " + desc, e); badClasses.add(desc); } } referencedClassSet.removeAll(badClasses); bugReporter.getProjectStats().setReferencedClasses(referencedClassSet.size()); for (Iterator<AnalysisPass> passIterator = executionPlan.passIterator(); passIterator.hasNext();) { AnalysisPass pass = passIterator.next(); yourkitController.advanceGeneration("Pass " + passCount); // The first pass is generally a non-reporting pass which // gathers information about referenced classes. boolean isNonReportingFirstPass = multiplePasses && passCount == 0; // Instantiate the detectors Detector2[] detectorList = pass.instantiateDetector2sInPass(bugReporter); // If there are multiple passes, then on the first pass, // we apply detectors to all classes referenced by the application classes. // On subsequent passes, we apply detector only to application classes. Collection<ClassDescriptor> classCollection = (isNonReportingFirstPass) ? referencedClassSet : appClassList; AnalysisContext.currentXFactory().canonicalizeAll(); if (PROGRESS || LIST_ORDER) { System.out.println("Pass " + (passCount) + ": " + classCollection.size() + " classes"); XFactory.profile(); } if (!isNonReportingFirstPass) { OutEdges<ClassDescriptor> outEdges = new OutEdges<ClassDescriptor>() { public Collection<ClassDescriptor> getOutEdges(ClassDescriptor e) { try { XClass classNameAndInfo = Global.getAnalysisCache().getClassAnalysis(XClass.class, e); if (classNameAndInfo instanceof ClassNameAndSuperclassInfo) { return ((ClassNameAndSuperclassInfo) classNameAndInfo).getCalledClassDescriptorList(); } assert false; return Collections.<ClassDescriptor>emptyList(); } catch (CheckedAnalysisException e2) { AnalysisContext.logError("error while analyzing " + e.getClassName(), e2); return Collections.<ClassDescriptor>emptyList(); } } }; List<ClassDescriptor> result = sortByCallGraph(classCollection, outEdges); classCollection = result; } if (LIST_ORDER) { System.out.println("Analysis order:"); for (ClassDescriptor c : classCollection) { System.out.println(" " + c); } } AnalysisContext.currentAnalysisContext().updateDatabases(passCount); progress.startAnalysis(classCollection.size()); int count = 0; Global.getAnalysisCache().purgeAllMethodAnalysis(); Global.getAnalysisCache().purgeClassAnalysis(FBClassReader.class); for (ClassDescriptor classDescriptor : classCollection) { if (PROGRESS) { System.out.printf("%d/%d %d/%d %s%n", passCount, executionPlan.getNumPasses(), count, classCollection.size(), classDescriptor); count++; } // Check to see if class is excluded by the class screener. // In general, we do not want to screen classes from the // first pass, even if they would otherwise be excluded. if ((SCREEN_FIRST_PASS_CLASSES || !isNonReportingFirstPass) && !classScreener.matches(classDescriptor.toResourceName())) { if (DEBUG) { System.out.println("*** Excluded by class screener"); } continue; } boolean isHuge = AnalysisContext.currentAnalysisContext().isTooBig(classDescriptor); if (isHuge && AnalysisContext.currentAnalysisContext().isApplicationClass(classDescriptor)) { bugReporter.reportBug(new BugInstance("SKIPPED_CLASS_TOO_BIG", Priorities.NORMAL_PRIORITY).addClass(classDescriptor)); } currentClassName = ClassName.toDottedClassName(classDescriptor.getClassName()); notifyClassObservers(classDescriptor); for (Detector2 detector : detectorList) { if (Thread.interrupted()) { throw new InterruptedException(); } if (isHuge && !FirstPassDetector.class.isAssignableFrom(detector.getClass())) { continue; } if (DEBUG) { System.out.println("Applying " + detector.getDetectorClassName() + " to " + classDescriptor); //System.out.println("foo: " + NonReportingDetector.class.isAssignableFrom(detector.getClass()) + ", bar: " + detector.getClass().getName()); } try { profiler.start(detector.getClass()); detector.visitClass(classDescriptor); } catch (ClassFormatException e) { logRecoverableException(classDescriptor, detector, e); } catch (MissingClassException e) { Global.getAnalysisCache().getErrorLogger().reportMissingClass(e.getClassDescriptor()); } catch (CheckedAnalysisException e) { logRecoverableException(classDescriptor, detector, e); } catch (RuntimeException e) { logRecoverableException(classDescriptor, detector, e); } finally { profiler.end(detector.getClass()); } } progress.finishClass(); } if (!passIterator.hasNext()) yourkitController.captureMemorySnapshot(); // Call finishPass on each detector for (Detector2 detector : detectorList) { detector.finishPass(); } progress.finishPerClassAnalysis(); passCount++; } // Flush any queued bug reports bugReporter.finish(); // if (baselineBugs != null) new Update().removeBaselineBugs(baselineBugs, bugReporter.); // Flush any queued error reports bugReporter.reportQueuedErrors(); } finally { profiler.end(this.getClass()); } } /** * Notify all IClassObservers that we are visiting given class. * * @param classDescriptor the class being visited */ private void notifyClassObservers(ClassDescriptor classDescriptor) { for (IClassObserver observer : classObserverList) { observer.observeClass(classDescriptor); } } /** * Report an exception that occurred while analyzing a class * with a detector. * * @param classDescriptor class being analyzed * @param detector detector doing the analysis * @param e the exception */ private void logRecoverableException( ClassDescriptor classDescriptor, Detector2 detector, Throwable e) { bugReporter.logError("Exception analyzing " + classDescriptor.toDottedClassName() + " using detector " + detector.getDetectorClassName(), e); } public static void main(String[] args) throws Exception { // Sanity-check the loaded BCEL classes if(!CheckBcel.check()) { System.exit(1); } // Create FindBugs2 engine FindBugs2 findBugs = new FindBugs2(); // Parse command line and configure the engine TextUICommandLine commandLine = new TextUICommandLine(); FindBugs.processCommandLine(commandLine, args, findBugs); // Away we go! FindBugs.runMain(findBugs, commandLine); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setAbridgedMessages(boolean) */ public void setAbridgedMessages(boolean xmlWithAbridgedMessages) { analysisOptions.abridgedMessages = xmlWithAbridgedMessages; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setMergeSimilarWarnings(boolean) */ public void setMergeSimilarWarnings(boolean mergeSimilarWarnings) { this.analysisOptions.mergeSimilarWarnings = mergeSimilarWarnings; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IFindBugsEngine#setApplySuppression(boolean) */ public void setApplySuppression(boolean applySuppression) { this.analysisOptions.applySuppression = applySuppression; } public void setRankThreshold(int rankThreshold) { this.rankThreshold = rankThreshold; } public void finishSettings() { if (analysisOptions.applySuppression) { BugReporter origBugReporter = bugReporter.getDelegate(); BugReporter filterBugReporter = new FilterBugReporter(origBugReporter, getProject().getSuppressionFilter(), false); bugReporter.setDelegate(filterBugReporter); } } }