All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.umd.cs.findbugs.FindBugs2 Maven / Gradle / Ivy

The newest version!
/*
 * 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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.asm.FBClassReader;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.AnalysisException;
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.jsr305.TypeQualifierAnnotation;
import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierApplications;
import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierValue;
import edu.umd.cs.findbugs.bugReporter.BugReporterDecorator;
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.ICodeBaseEntry;
import edu.umd.cs.findbugs.classfile.MissingClassException;
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.detect.NoteSuppressedWarnings;
import edu.umd.cs.findbugs.filter.FilterException;
import edu.umd.cs.findbugs.io.IO;
import edu.umd.cs.findbugs.log.Profiler;
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 IFindBugsEngine, AutoCloseable {
    private static final Logger LOG = LoggerFactory.getLogger(FindBugs2.class);

    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");

    public static final boolean MULTI_THREAD = SystemProperties.getBoolean("spotbugs.experimental.multiThread");

    public static final String PROP_FINDBUGS_HOST_APP = "findbugs.hostApp";
    public static final String PROP_FINDBUGS_HOST_APP_VERSION = "findbugs.hostAppVersion";

    private int rankThreshold;

    private List classObserverList;

    private BugReporter bugReporter;

    private ErrorCountingBugReporter errorCountingBugReporter;

    private Project project;

    private IClassFactory classFactory;

    private IClassPath classPath;

    private List appClassList;

    private Collection referencedClassSet;

    private DetectorFactoryCollection detectorFactoryCollection;

    private ExecutionPlan executionPlan;

    private String currentClassName;

    private FindBugsProgress progressReporter;

    private IClassScreener classScreener;

    private final AnalysisOptions analysisOptions = new AnalysisOptions(true);

    private final ExecutorService service;

    /**
     * Constructor that uses {@link CurrentThreadExecutorService} to keep backward compatibility with SpotBugs 3.1.
     *
     * @since 3.1
     */
    public FindBugs2() {
        this(new CurrentThreadExecutorService());
    }

    /**
     * @param service
     *            The non-null {@link ExecutorService} instance to execute analysis. Caller is responsible for shutting
     *            it down.
     * @since 4.0
     */
    public FindBugs2(@NonNull ExecutorService service) {
        this.service = Objects.requireNonNull(service, "Given ExecutorService cannot be null.");
        this.classObserverList = new LinkedList<>();
        this.analysisOptions.analysisFeatureSettingList = FindBugs.DEFAULT_EFFORT;
        this.progressReporter = new NoOpFindBugsProgress();

        // By default, do not exclude any classes via the class screener
        this.classScreener = new IClassScreener() {
            @Override
            public boolean matches(String fileName) {
                return true;
            }

            @Override
            public boolean vacuous() {
                return true;
            }
        };

        String hostApp = System.getProperty(PROP_FINDBUGS_HOST_APP);
        String hostAppVersion = null;
        if (hostApp == null || hostApp.trim().length() <= 0) {
            hostApp = "FindBugs TextUI";
            hostAppVersion = System.getProperty(PROP_FINDBUGS_HOST_APP_VERSION);
        }
        if (hostAppVersion == null) {
            hostAppVersion = "";
        }
        Version.registerApplication(hostApp, hostAppVersion);

        // 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 = BugRanker.VISIBLE_RANK_MAX;
    }

    /**
     * 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.
     */
    @Override
    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
     */
    @Override
    public void execute() throws IOException, InterruptedException {

        if (FindBugs.isNoAnalysis()) {
            throw new UnsupportedOperationException("This FindBugs invocation was started without analysis capabilities");
        }

        Profiler profiler = bugReporter.getProjectStats().getProfiler();

        try {
            try {
                // Get the class factory for creating classpath/codebase/etc.
                classFactory = ClassFactory.instance();

                // The class path object
                createClassPath();

                progressReporter.reportNumberOfArchives(project.getFileCount() + project.getNumAuxClasspathEntries());
                profiler.start(this.getClass());

                // The analysis cache object
                createAnalysisCache();

                // Create BCEL compatibility layer
                createAnalysisContext(project, appClassList, analysisOptions.sourceInfoFileName);

                // 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
                setAppClassList(appClassList);

                // 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();

                for (Plugin p : detectorFactoryCollection.plugins()) {
                    for (ComponentPlugin brp : p.getComponentPlugins(BugReporterDecorator.class)) {
                        if (brp.isEnabledByDefault() && !brp.isNamed(explicitlyDisabledBugReporterDecorators)
                                || brp.isNamed(explicitlyEnabledBugReporterDecorators)) {
                            bugReporter = BugReporterDecorator.construct(brp, bugReporter);
                        }
                    }
                }
                if (!classScreener.vacuous()) {
                    bugReporter = new DelegatingBugReporter(bugReporter) {

                        @Override
                        public void reportBug(@Nonnull BugInstance bugInstance) {
                            String className = bugInstance.getPrimaryClass().getClassName();
                            String resourceName = ClassName.toSlashedClassName(className) + ".class";
                            if (classScreener.matches(resourceName)) {
                                this.getDelegate().reportBug(bugInstance);
                            }
                        }
                    };
                }

                if (executionPlan.isActive(NoteSuppressedWarnings.class)) {
                    SuppressionMatcher m = AnalysisContext.currentAnalysisContext().getSuppressionMatcher();
                    bugReporter = new FilterBugReporter(bugReporter, m, false);
                }

                if (appClassList.size() == 0) {
                    Map codebase = classPath.getApplicationCodebaseEntries();
                    if (analysisOptions.noClassOk) {
                        System.err.println("No classfiles specified; output will have no warnings");
                    } else if (codebase.isEmpty()) {
                        throw new IOException("No files to analyze could be opened");
                    } 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 {
                clearCaches();
                profiler.end(this.getClass());
                profiler.report();
            }
        } catch (IOException e) {
            bugReporter.reportQueuedErrors();
            throw e;
        }
    }

    /**
     * Protected to allow Eclipse plugin remember some cache data for later reuse
     */
    protected void clearCaches() {
        DescriptorFactory.clearInstance();
        ObjectTypeFactory.clearInstance();
        TypeQualifierApplications.clearInstance();
        TypeQualifierAnnotation.clearInstance();
        TypeQualifierValue.clearInstance();
        // Make sure the codebases on the classpath are closed
        AnalysisContext.removeCurrentAnalysisContext();
        Global.removeAnalysisCacheForCurrentThread();
        IO.close(classPath);
    }

    /**
     * 

* To avoid cyclic cross-references and allow GC after engine is no longer needed. (used by Eclipse plugin) *

*

* Caller probably need to shutdown the {@link ExecutorService} instance provided at constructor. *

*/ public void dispose() { if (executionPlan != null) { executionPlan.dispose(); } if (appClassList != null) { appClassList.clear(); } if (classObserverList != null) { classObserverList.clear(); } if (referencedClassSet != null) { referencedClassSet.clear(); } analysisOptions.analysisFeatureSettingList = null; bugReporter = null; classFactory = null; IO.close(classPath); classPath = null; classScreener = null; detectorFactoryCollection = null; executionPlan = null; progressReporter = null; IO.close(project); project = null; analysisOptions.userPreferences = null; } @Override public BugReporter getBugReporter() { return bugReporter; } @Override public Project getProject() { return project; } @Override public void addClassObserver(IClassObserver classObserver) { classObserverList.add(classObserver); } @Override public void addFilter(String filterFileName, boolean include) throws IOException, FilterException { bugReporter = FindBugs.configureFilter(bugReporter, filterFileName, include); } @Override public void excludeBaselineBugs(String baselineBugs) throws IOException, DocumentException { bugReporter = FindBugs.configureBaselineFilter(bugReporter, baselineBugs); } @Override public void enableTrainingInput(String trainingInputDir) { this.analysisOptions.trainingInputDir = trainingInputDir; } @Override public void enableTrainingOutput(String trainingOutputDir) { this.analysisOptions.trainingOutputDir = trainingOutputDir; } @Override public int getBugCount() { return errorCountingBugReporter.getBugCount(); } @Override public String getCurrentClass() { return currentClassName; } @Override public int getErrorCount() { return errorCountingBugReporter.getErrorCount(); } @Override public int getMissingClassCount() { return errorCountingBugReporter.getMissingClassCount(); } @Override public String getReleaseName() { return analysisOptions.releaseName; } @Override public String getProjectName() { return analysisOptions.projectName; } @Override public void setProjectName(String name) { analysisOptions.projectName = name; } @Override public void setAnalysisFeatureSettings(AnalysisFeatureSetting[] settingList) { this.analysisOptions.analysisFeatureSettingList = settingList; } @Override public void setBugReporter(BugReporter bugReporter) { this.bugReporter = this.errorCountingBugReporter = new ErrorCountingBugReporter(bugReporter); addClassObserver(bugReporter); } @Override public void setClassScreener(IClassScreener classScreener) { this.classScreener = classScreener; } @Override public void setProgressCallback(FindBugsProgress progressCallback) { this.progressReporter = progressCallback; } @Override public void setProject(Project project) { this.project = project; } @Override public void setRelaxedReportingMode(boolean relaxedReportingMode) { this.analysisOptions.relaxedReportingMode = relaxedReportingMode; } @Override public void setReleaseName(String releaseName) { this.analysisOptions.releaseName = releaseName; } @Override public void setSourceInfoFile(String sourceInfoFile) { this.analysisOptions.sourceInfoFileName = sourceInfoFile; } @Override public void setUserPreferences(UserPreferences userPreferences) { this.analysisOptions.userPreferences = userPreferences; // TODO should set it here too, but gui2 seems to have issues with it // setAnalysisFeatureSettings(userPreferences.getAnalysisFeatureSettings()); configureFilters(userPreferences); } protected void configureFilters(UserPreferences userPreferences) { IllegalArgumentException deferredError = null; Set> excludeBugFiles = userPreferences.getExcludeBugsFiles().entrySet(); for (Entry entry : excludeBugFiles) { if (entry.getValue() == null || !entry.getValue()) { continue; } try { excludeBaselineBugs(entry.getKey()); } catch (Exception e) { String message = "Unable to read filter: " + entry.getKey() + " : " + e.getMessage(); if (getBugReporter() != null) { getBugReporter().logError(message, e); } else if (deferredError == null) { deferredError = new IllegalArgumentException(message, e); } } } Set> includeFilterFiles = userPreferences.getIncludeFilterFiles().entrySet(); for (Entry entry : includeFilterFiles) { if (entry.getValue() == null || !entry.getValue()) { continue; } try { addFilter(entry.getKey(), true); } catch (Exception e) { String message = "Unable to read filter: " + entry.getKey() + " : " + e.getMessage(); if (getBugReporter() != null) { getBugReporter().logError(message, e); } else if (deferredError == null) { deferredError = new IllegalArgumentException(message, e); } } } Set> excludeFilterFiles = userPreferences.getExcludeFilterFiles().entrySet(); for (Entry entry : excludeFilterFiles) { Boolean value = entry.getValue(); if (value == null || !value) { continue; } String excludeFilterFile = entry.getKey(); try { addFilter(excludeFilterFile, false); } catch (Exception e) { String message = "Unable to read filter: " + excludeFilterFile + " : " + e.getMessage(); if (getBugReporter() != null) { getBugReporter().logError(message, e); } else if (deferredError == null) { deferredError = new IllegalArgumentException(message, e); } } } if (deferredError != null) { throw deferredError; } } @Override public boolean emitTrainingOutput() { return analysisOptions.trainingOutputDir != null; } @Override public UserPreferences getUserPreferences() { return analysisOptions.userPreferences; } /** * Create the classpath object. */ private void createClassPath() { classPath = classFactory.createClassPath(); } @Override public String getTrainingInputDir() { return analysisOptions.trainingInputDir; } @Override public String getTrainingOutputDir() { return analysisOptions.trainingOutputDir; } @Override public boolean useTrainingInput() { return analysisOptions.trainingInputDir != null; } @Override public void setScanNestedArchives(boolean scanNestedArchives) { this.analysisOptions.scanNestedArchives = scanNestedArchives; } @Override public void setNoClassOk(boolean noClassOk) { this.analysisOptions.noClassOk = noClassOk; } /** * Create the analysis cache object and register it for current execution thread. *

* This method is protected to allow clients override it and possibly reuse * some previous analysis data (for Eclipse interactive re-build) * * @throws IOException * if error occurs registering analysis engines in a plugin */ protected IAnalysisCache createAnalysisCache() throws IOException { IAnalysisCache 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); return 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 i = detectorFactoryCollection.pluginIterator(); i.hasNext();) { Plugin plugin = i.next(); Class 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); { HashSet seen = new HashSet<>(); for (String path : project.getFileArray()) { if (seen.add(path)) { builder.addCodeBase(classFactory.createFilesystemCodeBaseLocator(path), true); } } for (String path : project.getAuxClasspathEntryList()) { if (seen.add(path)) { builder.addCodeBase(classFactory.createFilesystemCodeBaseLocator(path), false); } } } builder.scanNestedArchives(analysisOptions.scanNestedArchives); builder.build(classPath, progressReporter); 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. List pathNames = new ArrayList<>(); for (Iterator i = classPath.appCodeBaseIterator(); i.hasNext();) { ICodeBase appCodeBase = i.next(); if (appCodeBase.containsSourceFiles()) { String pathName = appCodeBase.getPathName(); if (pathName != null) { pathNames.add(pathName); } } project.addTimestamp(appCodeBase.getLastModifiedTime()); } project.addSourceDirs(pathNames); } private void buildReferencedClassSet() throws InterruptedException { // XXX: should drive progress dialog (scanning phase)? if (PROGRESS) { System.out.println("Adding referenced classes"); } Set referencedPackageSet = new HashSet<>(); LinkedList workList = new LinkedList<>(appClassList); Set seen = new HashSet<>(); Set appClassSet = new HashSet<>(appClassList); Set badAppClassSet = new HashSet<>(); HashSet knownDescriptors = new HashSet<>(DescriptorFactory.instance() .getAllClassDescriptors()); int count = 0; Set addedToWorkList = new HashSet<>(appClassList); // add fields //noinspection ConstantIfStatement /* 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); } } ClassDescriptor enclosingClass = classNameAndInfo.getImmediateEnclosingClass(); if (enclosingClass != null && addedToWorkList.add(enclosingClass)) { workList.addLast(enclosingClass); } } 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()); } referencedClassSet = new ArrayList<>(DescriptorFactory.instance().getAllClassDescriptors()); // 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"); for (ClassDescriptor d : referencedClassSet) { System.out.println(" " + d); } } } public List sortByCallGraph(Collection classList, OutEdges outEdges) { List 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 appClassList, @CheckForNull String sourceInfoFileName) throws IOException { AnalysisContext analysisContext = new AnalysisContext(project); // Make this the current analysis context AnalysisContext.setCurrentAnalysisContext(analysisContext); // Make the AnalysisCache the backing store for // the BCEL Repository analysisContext.clearRepository(); // If needed, load SourceInfoMap if (sourceInfoFileName != null) { SourceInfoMap sourceInfoMap = analysisContext.getSourceInfoMap(); sourceInfoMap.read(new FileInputStream(sourceInfoFileName)); } } public static void setAppClassList(List appClassList) { AnalysisContext analysisContext = AnalysisContext .currentAnalysisContext(); analysisContext.setAppClassList(appClassList); } /** * 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 forcedEnabled = new HashSet<>(); @Override public boolean choose(DetectorFactory factory) { boolean result = FindBugs.isDetectorEnabled(FindBugs2.this, factory, rankThreshold) || forcedEnabled.contains(factory); if (ExecutionPlan.DEBUG) { System.out.printf(" %6s %s %n", result, factory.getShortName()); } return result; } @Override public void enable(DetectorFactory factory) { forcedEnabled.add(factory); factory.setEnabledButNonReporting(true); } }; executionPlan.setDetectorFactoryChooser(detectorFactoryChooser); if (ExecutionPlan.DEBUG) { System.out.println("rank threshold is " + rankThreshold); } // Add plugins for (Iterator 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"); } } /** * 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(); } progressReporter.predictPassCount(classesPerPass); XFactory factory = AnalysisContext.currentXFactory(); Collection badClasses = new LinkedList<>(); 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); } } if (!badClasses.isEmpty()) { referencedClassSet = new LinkedHashSet<>(referencedClassSet); referencedClassSet.removeAll(badClasses); } long startTime = System.currentTimeMillis(); bugReporter.getProjectStats().setReferencedClasses(referencedClassSet.size()); for (Iterator passIterator = executionPlan.passIterator(); passIterator.hasNext();) { AnalysisPass pass = passIterator.next(); // 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 classCollection = (isNonReportingFirstPass) ? referencedClassSet : appClassList; AnalysisContext.currentXFactory().canonicalizeAll(); if (PROGRESS || LIST_ORDER) { System.out.printf("%6d : Pass %d: %d classes%n", (System.currentTimeMillis() - startTime) / 1000, passCount, classCollection .size()); if (DEBUG) { XFactory.profile(); } } if (!isNonReportingFirstPass) { OutEdges outEdges = e -> { try { XClass classNameAndInfo = Global.getAnalysisCache().getClassAnalysis(XClass.class, e); return classNameAndInfo.getCalledClassDescriptors(); } catch (CheckedAnalysisException e2) { AnalysisContext.logError("error while analyzing " + e.getClassName(), e2); return Collections.emptyList(); } }; classCollection = sortByCallGraph(classCollection, outEdges); } if (LIST_ORDER) { System.out.println("Analysis order:"); for (ClassDescriptor c : classCollection) { System.out.println(" " + c); } } AnalysisContext currentAnalysisContext = AnalysisContext.currentAnalysisContext(); currentAnalysisContext.updateDatabases(passCount); progressReporter.startAnalysis(classCollection.size()); int count = 0; Global.getAnalysisCache().purgeAllMethodAnalysis(); Global.getAnalysisCache().purgeClassAnalysis(FBClassReader.class); for (ClassDescriptor classDescriptor : classCollection) { long classStartNanoTime = 0; if (PROGRESS) { classStartNanoTime = System.nanoTime(); System.out.printf("%6d %d/%d %d/%d %s%n", (System.currentTimeMillis() - startTime) / 1000, 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 = currentAnalysisContext.isTooBig(classDescriptor); if (isHuge && currentAnalysisContext.isApplicationClass(classDescriptor)) { bugReporter.reportBug(new BugInstance("SKIPPED_CLASS_TOO_BIG", Priorities.NORMAL_PRIORITY) .addClass(classDescriptor)); } currentClassName = ClassName.toDottedClassName(classDescriptor.getClassName()); notifyClassObservers(classDescriptor); profiler.startContext(currentClassName); currentAnalysisContext.setClassBeingAnalyzed(classDescriptor); try { Collection> tasks = Arrays.stream(detectorList).map(detector -> (Callable) () -> { if (Thread.interrupted()) { throw new InterruptedException(); } if (isHuge && !FirstPassDetector.class.isAssignableFrom(detector.getClass())) { return null; } LOG.debug("Applying {} to {}", detector.getDetectorClassName(), classDescriptor); try { profiler.start(detector.getClass()); detector.visitClass(classDescriptor); } catch (MissingClassException e) { Global.getAnalysisCache().getErrorLogger().reportMissingClass(e.getClassDescriptor()); } catch (CheckedAnalysisException | RuntimeException e) { logRecoverableException(classDescriptor, detector, e); } finally { profiler.end(detector.getClass()); } return null; }).collect(Collectors.toList()); service.invokeAll(tasks).forEach(future -> { try { future.get(); } catch (InterruptedException e) { LOG.warn("Thread interrupted during analysis", e); Thread.currentThread().interrupt(); } catch (ExecutionException e) { throw new AnalysisException("Exeption was thrown during analysis", e); } }); if (Thread.interrupted()) { throw new InterruptedException(); } } finally { progressReporter.finishClass(); profiler.endContext(currentClassName); currentAnalysisContext.clearClassBeingAnalyzed(); if (PROGRESS) { long usecs = (System.nanoTime() - classStartNanoTime) / 1000; if (usecs > 15000) { int classSize = currentAnalysisContext.getClassSize(classDescriptor); long speed = usecs / classSize; if (speed > 15) { System.out.printf(" %6d usecs/byte %6d msec %6d bytes %d pass %s%n", speed, usecs / 1000, classSize, passCount, classDescriptor); } } } } } // Call finishPass on each detector for (Detector2 detector : detectorList) { detector.finishPass(); } progressReporter.finishPerClassAnalysis(); passCount++; } } finally { bugReporter.finish(); bugReporter.reportQueuedErrors(); profiler.end(this.getClass()); if (PROGRESS) { System.out.println("Analysis completed"); } } } /** * 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); } final ExecutorService service; if (MULTI_THREAD) { LOG.warn("Multi-thread analysis is still experimental!"); service = Executors.newCachedThreadPool(); } else { service = new CurrentThreadExecutorService(); } // Create FindBugs2 engine try (FindBugs2 findBugs = new FindBugs2(service)) { // Parse command line and configure the engine TextUICommandLine commandLine = new TextUICommandLine(); FindBugs.processCommandLine(commandLine, args, findBugs); boolean justPrintConfiguration = commandLine.justPrintConfiguration(); if (justPrintConfiguration || commandLine.justPrintVersion()) { Version.printVersion(justPrintConfiguration); return; } // Away we go! FindBugs.runMain(findBugs, commandLine); } finally { service.shutdown(); } } @Override public void setAbridgedMessages(boolean xmlWithAbridgedMessages) { analysisOptions.abridgedMessages = xmlWithAbridgedMessages; } @Override public void setMergeSimilarWarnings(boolean mergeSimilarWarnings) { this.analysisOptions.mergeSimilarWarnings = mergeSimilarWarnings; } @Override public void setApplySuppression(boolean applySuppression) { this.analysisOptions.applySuppression = applySuppression; } @Override public void setRankThreshold(int rankThreshold) { this.rankThreshold = rankThreshold; } @Override public void finishSettings() { if (analysisOptions.applySuppression) { bugReporter = new FilterBugReporter(bugReporter, getProject().getSuppressionFilter(), false); } } @Nonnull Set explicitlyEnabledBugReporterDecorators = Collections.emptySet(); @Nonnull Set explicitlyDisabledBugReporterDecorators = Collections.emptySet(); @Override public void setBugReporterDecorators(Set explicitlyEnabled, Set explicitlyDisabled) { explicitlyEnabledBugReporterDecorators = explicitlyEnabled; explicitlyDisabledBugReporterDecorators = explicitlyDisabled; } @Override public void close() { dispose(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy