1   /*
2    * FindBugs - Find Bugs in Java programs
3    * Copyright (C) 2006-2008 University of Maryland
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  package edu.umd.cs.findbugs;
21  
22  import java.io.FileInputStream;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.LinkedHashSet;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Map.Entry;
34  import java.util.Set;
35  
36  import javax.annotation.CheckForNull;
37  import javax.annotation.Nonnull;
38  
39  import org.apache.bcel.classfile.ClassFormatException;
40  import org.dom4j.DocumentException;
41  
42  import edu.umd.cs.findbugs.asm.FBClassReader;
43  import edu.umd.cs.findbugs.ba.AnalysisContext;
44  import edu.umd.cs.findbugs.ba.AnalysisFeatures;
45  import edu.umd.cs.findbugs.ba.ObjectTypeFactory;
46  import edu.umd.cs.findbugs.ba.SourceInfoMap;
47  import edu.umd.cs.findbugs.ba.XClass;
48  import edu.umd.cs.findbugs.ba.XFactory;
49  import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierAnnotation;
50  import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierApplications;
51  import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierValue;
52  import edu.umd.cs.findbugs.bugReporter.BugReporterDecorator;
53  import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
54  import edu.umd.cs.findbugs.classfile.ClassDescriptor;
55  import edu.umd.cs.findbugs.classfile.DescriptorFactory;
56  import edu.umd.cs.findbugs.classfile.Global;
57  import edu.umd.cs.findbugs.classfile.IAnalysisCache;
58  import edu.umd.cs.findbugs.classfile.IAnalysisEngineRegistrar;
59  import edu.umd.cs.findbugs.classfile.IClassFactory;
60  import edu.umd.cs.findbugs.classfile.IClassObserver;
61  import edu.umd.cs.findbugs.classfile.IClassPath;
62  import edu.umd.cs.findbugs.classfile.IClassPathBuilder;
63  import edu.umd.cs.findbugs.classfile.ICodeBase;
64  import edu.umd.cs.findbugs.classfile.ICodeBaseEntry;
65  import edu.umd.cs.findbugs.classfile.MissingClassException;
66  import edu.umd.cs.findbugs.classfile.impl.ClassFactory;
67  import edu.umd.cs.findbugs.config.AnalysisFeatureSetting;
68  import edu.umd.cs.findbugs.config.UserPreferences;
69  import edu.umd.cs.findbugs.detect.NoteSuppressedWarnings;
70  import edu.umd.cs.findbugs.filter.FilterException;
71  import edu.umd.cs.findbugs.log.Profiler;
72  import edu.umd.cs.findbugs.plan.AnalysisPass;
73  import edu.umd.cs.findbugs.plan.ExecutionPlan;
74  import edu.umd.cs.findbugs.plan.OrderingConstraintException;
75  import edu.umd.cs.findbugs.util.ClassName;
76  import edu.umd.cs.findbugs.util.TopologicalSort.OutEdges;
77  
78  /**
79   * FindBugs driver class. Orchestrates the analysis of a project, collection of
80   * results, etc.
81   *
82   * @author David Hovemeyer
83   */
84  public class FindBugs2 implements IFindBugsEngine {
85      private static final boolean LIST_ORDER = SystemProperties.getBoolean("findbugs.listOrder");
86  
87      private static final boolean VERBOSE = SystemProperties.getBoolean("findbugs.verbose");
88  
89      public static final boolean DEBUG = VERBOSE || SystemProperties.getBoolean("findbugs.debug");
90  
91      public static final boolean PROGRESS = DEBUG || SystemProperties.getBoolean("findbugs.progress");
92  
93      private static final boolean SCREEN_FIRST_PASS_CLASSES = SystemProperties.getBoolean("findbugs.screenFirstPass");
94  
95      public static final String PROP_FINDBUGS_HOST_APP = "findbugs.hostApp";
96      public static final String PROP_FINDBUGS_HOST_APP_VERSION = "findbugs.hostAppVersion";
97  
98      private int rankThreshold;
99  
100     private List<IClassObserver> classObserverList;
101 
102     private BugReporter bugReporter;
103 
104     private ErrorCountingBugReporter errorCountingBugReporter;
105 
106     private Project project;
107 
108     private IClassFactory classFactory;
109 
110     private IClassPath classPath;
111 
112     private List<ClassDescriptor> appClassList;
113 
114     private Collection<ClassDescriptor> referencedClassSet;
115 
116     private DetectorFactoryCollection detectorFactoryCollection;
117 
118     private ExecutionPlan executionPlan;
119 
120     private String currentClassName;
121 
122     private FindBugsProgress progress;
123 
124     private IClassScreener classScreener;
125 
126     private final AnalysisOptions analysisOptions = new AnalysisOptions(true);
127 
128     /**
129      * Constructor.
130      */
131     public FindBugs2() {
132         this.classObserverList = new LinkedList<>();
133         this.analysisOptions.analysisFeatureSettingList = FindBugs.DEFAULT_EFFORT;
134         this.progress = new NoOpFindBugsProgress();
135 
136         // By default, do not exclude any classes via the class screener
137         this.classScreener = new IClassScreener() {
138             @Override
139             public boolean matches(String fileName) {
140                 return true;
141             }
142 
143             @Override
144             public boolean vacuous() {
145                 return true;
146             }
147         };
148 
149         String hostApp = System.getProperty(PROP_FINDBUGS_HOST_APP);
150         String hostAppVersion = null;
151         if (hostApp == null || hostApp.trim().length() <= 0) {
152             hostApp = "FindBugs TextUI";
153             hostAppVersion = System.getProperty(PROP_FINDBUGS_HOST_APP_VERSION);
154         }
155         if (hostAppVersion == null) {
156             hostAppVersion = "";
157         }
158         Version.registerApplication(hostApp, hostAppVersion);
159 
160         // By default, we do not want to scan nested archives
161         this.analysisOptions.scanNestedArchives = false;
162         // bug 2815983: no bugs are reported anymore
163         // there is no info which value should be default, so using the any one
164         rankThreshold = BugRanker.VISIBLE_RANK_MAX;
165     }
166 
167     /**
168      * Set the detector factory collection to be used by this FindBugs2 engine.
169      * This method should be called before the execute() method is called.
170      *
171      * @param detectorFactoryCollection
172      *            The detectorFactoryCollection to set.
173      */
174     @Override
175     public void setDetectorFactoryCollection(DetectorFactoryCollection detectorFactoryCollection) {
176         this.detectorFactoryCollection = detectorFactoryCollection;
177     }
178 
179     /**
180      * Execute the analysis. For obscure reasons, CheckedAnalysisExceptions are
181      * re-thrown as IOExceptions. However, these can only happen during the
182      * setup phase where we scan codebases for classes.
183      *
184      * @throws IOException
185      * @throws InterruptedException
186      */
187     @Override
188     public void execute() throws IOException, InterruptedException {
189 
190         if (FindBugs.isNoAnalysis()) {
191             throw new UnsupportedOperationException("This FindBugs invocation was started without analysis capabilities");
192         }
193 
194         Profiler profiler = bugReporter.getProjectStats().getProfiler();
195 
196         try {
197             try {
198                 // Get the class factory for creating classpath/codebase/etc.
199                 classFactory = ClassFactory.instance();
200 
201                 // The class path object
202                 createClassPath();
203 
204                 progress.reportNumberOfArchives(project.getFileCount() + project.getNumAuxClasspathEntries());
205                 profiler.start(this.getClass());
206 
207                 // The analysis cache object
208                 createAnalysisCache();
209 
210                 // Create BCEL compatibility layer
211                 createAnalysisContext(project, appClassList, analysisOptions.sourceInfoFileName);
212 
213                 // Discover all codebases in classpath and
214                 // enumerate all classes (application and non-application)
215                 buildClassPath();
216 
217 
218                 // Build set of classes referenced by application classes
219                 buildReferencedClassSet();
220 
221                 // Create BCEL compatibility layer
222                 setAppClassList(appClassList);
223 
224                 // Configure the BugCollection (if we are generating one)
225                 FindBugs.configureBugCollection(this);
226 
227                 // Enable/disabled relaxed reporting mode
228                 FindBugsAnalysisFeatures.setRelaxedMode(analysisOptions.relaxedReportingMode);
229                 FindBugsDisplayFeatures.setAbridgedMessages(analysisOptions.abridgedMessages);
230 
231                 // Configure training databases
232                 FindBugs.configureTrainingDatabases(this);
233 
234                 // Configure analysis features
235                 configureAnalysisFeatures();
236 
237                 // Create the execution plan (which passes/detectors to execute)
238                 createExecutionPlan();
239 
240                 for (Plugin p : detectorFactoryCollection.plugins()) {
241                     for (ComponentPlugin<BugReporterDecorator> brp
242                             : p.getComponentPlugins(BugReporterDecorator.class)) {
243                         if (brp.isEnabledByDefault() && !brp.isNamed(explicitlyDisabledBugReporterDecorators)
244                                 || brp.isNamed(explicitlyEnabledBugReporterDecorators)) {
245                             bugReporter = BugReporterDecorator.construct(brp, bugReporter);
246                         }
247                     }
248                 }
249                 if (!classScreener.vacuous()) {
250                     bugReporter = new DelegatingBugReporter(bugReporter) {
251 
252                         @Override
253                         public void reportBug(@Nonnull BugInstance bugInstance) {
254                             String className = bugInstance.getPrimaryClass().getClassName();
255                             String resourceName = className.replace('.', '/') + ".class";
256                             if (classScreener.matches(resourceName)) {
257                                 this.getDelegate().reportBug(bugInstance);
258                             }
259                         }
260                     };
261                 }
262 
263                 if (executionPlan.isActive(NoteSuppressedWarnings.class)) {
264                     SuppressionMatcher m = AnalysisContext.currentAnalysisContext().getSuppressionMatcher();
265                     bugReporter = new FilterBugReporter(bugReporter, m, false);
266                 }
267 
268                 if (appClassList.size() == 0) {
269                     Map<String, ICodeBaseEntry> codebase = classPath.getApplicationCodebaseEntries();
270                     if (analysisOptions.noClassOk) {
271                         System.err.println("No classfiles specified; output will have no warnings");
272                     } else if  (codebase.isEmpty()) {
273                         throw new IOException("No files to analyze could be opened");
274                     } else {
275                         throw new NoClassesFoundToAnalyzeException(classPath);
276                     }
277                 }
278 
279                 // Analyze the application
280                 analyzeApplication();
281             } catch (CheckedAnalysisException e) {
282                 IOException ioe = new IOException("IOException while scanning codebases");
283                 ioe.initCause(e);
284                 throw ioe;
285             } catch (OutOfMemoryError e) {
286                 System.err.println("Out of memory");
287                 System.err.println("Total memory: " + Runtime.getRuntime().maxMemory() / 1000000 + "M");
288                 System.err.println(" free memory: " + Runtime.getRuntime().freeMemory() / 1000000 + "M");
289 
290                 for (String s : project.getFileList()) {
291                     System.err.println("Analyzed: " + s);
292                 }
293                 for (String s : project.getAuxClasspathEntryList()) {
294                     System.err.println("     Aux: " + s);
295                 }
296                 throw e;
297             } finally {
298                 clearCaches();
299                 profiler.end(this.getClass());
300                 profiler.report();
301             }
302         } catch (IOException e) {
303             bugReporter.reportQueuedErrors();
304             throw e;
305         }
306     }
307 
308     /**
309      * Protected to allow Eclipse plugin remember some cache data for later reuse
310      */
311     protected void clearCaches() {
312         DescriptorFactory.clearInstance();
313         ObjectTypeFactory.clearInstance();
314         TypeQualifierApplications.clearInstance();
315         TypeQualifierAnnotation.clearInstance();
316         TypeQualifierValue.clearInstance();
317         // Make sure the codebases on the classpath are closed
318         AnalysisContext.removeCurrentAnalysisContext();
319         Global.removeAnalysisCacheForCurrentThread();
320         if (classPath != null) {
321             classPath.close();
322         }
323     }
324 
325     /**
326      * To avoid cyclic cross-references and allow GC after engine is not more
327      * needed. (used by Eclipse plugin)
328      */
329     public void dispose() {
330         if (executionPlan != null) {
331             executionPlan.dispose();
332         }
333         if (appClassList != null) {
334             appClassList.clear();
335         }
336         if (classObserverList != null) {
337             classObserverList.clear();
338         }
339         if (referencedClassSet != null) {
340             referencedClassSet.clear();
341         }
342         analysisOptions.analysisFeatureSettingList = null;
343         bugReporter = null;
344         classFactory = null;
345         classPath = null;
346         classScreener = null;
347         detectorFactoryCollection = null;
348         executionPlan = null;
349         progress = null;
350         project = null;
351         analysisOptions.userPreferences = null;
352     }
353 
354     @Override
355     public BugReporter getBugReporter() {
356         return bugReporter;
357     }
358 
359     @Override
360     public Project getProject() {
361         return project;
362     }
363 
364     @Override
365     public void addClassObserver(IClassObserver classObserver) {
366         classObserverList.add(classObserver);
367     }
368 
369     @Override
370     public void addFilter(String filterFileName, boolean include) throws IOException, FilterException {
371         bugReporter = FindBugs.configureFilter(bugReporter, filterFileName, include);
372     }
373 
374     @Override
375     public void excludeBaselineBugs(String baselineBugs) throws IOException, DocumentException {
376         bugReporter = FindBugs.configureBaselineFilter(bugReporter, baselineBugs);
377     }
378 
379     @Override
380     public void enableTrainingInput(String trainingInputDir) {
381         this.analysisOptions.trainingInputDir = trainingInputDir;
382     }
383 
384     @Override
385     public void enableTrainingOutput(String trainingOutputDir) {
386         this.analysisOptions.trainingOutputDir = trainingOutputDir;
387     }
388 
389     @Override
390     public int getBugCount() {
391         return errorCountingBugReporter.getBugCount();
392     }
393 
394     @Override
395     public String getCurrentClass() {
396         return currentClassName;
397     }
398 
399     @Override
400     public int getErrorCount() {
401         return errorCountingBugReporter.getErrorCount();
402     }
403 
404     @Override
405     public int getMissingClassCount() {
406         return errorCountingBugReporter.getMissingClassCount();
407     }
408 
409     @Override
410     public String getReleaseName() {
411         return analysisOptions.releaseName;
412     }
413 
414     @Override
415     public String getProjectName() {
416         return analysisOptions.projectName;
417     }
418 
419     @Override
420     public void setProjectName(String name) {
421         analysisOptions.projectName = name;
422     }
423 
424     @Override
425     public void setAnalysisFeatureSettings(AnalysisFeatureSetting[] settingList) {
426         this.analysisOptions.analysisFeatureSettingList = settingList;
427     }
428 
429     @Override
430     public void setBugReporter(BugReporter bugReporter) {
431         this.bugReporter = this.errorCountingBugReporter = new ErrorCountingBugReporter(bugReporter);
432 
433         addClassObserver(bugReporter);
434     }
435 
436     @Override
437     public void setClassScreener(IClassScreener classScreener) {
438         this.classScreener = classScreener;
439     }
440 
441     @Override
442     public void setProgressCallback(FindBugsProgress progressCallback) {
443         this.progress = progressCallback;
444     }
445 
446     @Override
447     public void setProject(Project project) {
448         this.project = project;
449     }
450 
451     @Override
452     public void setRelaxedReportingMode(boolean relaxedReportingMode) {
453         this.analysisOptions.relaxedReportingMode = relaxedReportingMode;
454     }
455 
456     @Override
457     public void setReleaseName(String releaseName) {
458         this.analysisOptions.releaseName = releaseName;
459     }
460 
461     @Override
462     public void setSourceInfoFile(String sourceInfoFile) {
463         this.analysisOptions.sourceInfoFileName = sourceInfoFile;
464     }
465 
466     @Override
467     public void setUserPreferences(UserPreferences userPreferences) {
468         this.analysisOptions.userPreferences = userPreferences;
469         // TODO should set it here too, but gui2 seems to have issues with it
470         // setAnalysisFeatureSettings(userPreferences.getAnalysisFeatureSettings());
471 
472         configureFilters(userPreferences);
473     }
474 
475     protected void configureFilters(UserPreferences userPreferences) {
476         IllegalArgumentException deferredError = null;
477         Set<Entry<String, Boolean>> excludeBugFiles = userPreferences.getExcludeBugsFiles().entrySet();
478         for (Entry<String, Boolean> entry : excludeBugFiles) {
479             if (entry.getValue() == null || !entry.getValue()) {
480                 continue;
481             }
482             try {
483                 excludeBaselineBugs(entry.getKey());
484             } catch (Exception e) {
485                 String message = "Unable to read filter: " + entry.getKey() + " : " + e.getMessage();
486                 if (getBugReporter() != null) {
487                     getBugReporter().logError(message, e);
488                 } else if (deferredError == null){
489                     deferredError = new IllegalArgumentException(message, e);
490                 }
491             }
492         }
493         Set<Entry<String, Boolean>> includeFilterFiles = userPreferences.getIncludeFilterFiles().entrySet();
494         for (Entry<String, Boolean> entry : includeFilterFiles) {
495             if (entry.getValue() == null || !entry.getValue()) {
496                 continue;
497             }
498             try {
499                 addFilter(entry.getKey(), true);
500             } catch (Exception e) {
501                 String message = "Unable to read filter: " + entry.getKey() + " : " + e.getMessage();
502                 if (getBugReporter() != null) {
503                     getBugReporter().logError(message, e);
504                 } else if (deferredError == null){
505                     deferredError = new IllegalArgumentException(message, e);
506                 }
507             }
508         }
509         Set<Entry<String, Boolean>> excludeFilterFiles = userPreferences.getExcludeFilterFiles().entrySet();
510 
511         for (Entry<String, Boolean> entry : excludeFilterFiles) {
512             Boolean value = entry.getValue();
513             if (value == null || !value) {
514                 continue;
515             }
516             String excludeFilterFile = entry.getKey();
517             try {
518                 addFilter(excludeFilterFile, false);
519             } catch (Exception e) {
520                 String message = "Unable to read filter: " + excludeFilterFile + " : " + e.getMessage();
521                 if (getBugReporter() != null) {
522                     getBugReporter().logError(message, e);
523                 } else if (deferredError == null){
524                     deferredError = new IllegalArgumentException(message, e);
525                 }
526             }
527         }
528         if (deferredError != null) {
529             throw deferredError;
530         }
531     }
532 
533     @Override
534     public boolean emitTrainingOutput() {
535         return analysisOptions.trainingOutputDir != null;
536     }
537 
538     @Override
539     public UserPreferences getUserPreferences() {
540         return analysisOptions.userPreferences;
541     }
542 
543     /**
544      * Create the classpath object.
545      */
546     private void createClassPath() {
547         classPath = classFactory.createClassPath();
548     }
549 
550     @Override
551     public String getTrainingInputDir() {
552         return analysisOptions.trainingInputDir;
553     }
554 
555     @Override
556     public String getTrainingOutputDir() {
557         return analysisOptions.trainingOutputDir;
558     }
559 
560     @Override
561     public boolean useTrainingInput() {
562         return analysisOptions.trainingInputDir != null;
563     }
564 
565     @Override
566     public void setScanNestedArchives(boolean scanNestedArchives) {
567         this.analysisOptions.scanNestedArchives = scanNestedArchives;
568     }
569 
570     @Override
571     public void setNoClassOk(boolean noClassOk) {
572         this.analysisOptions.noClassOk = noClassOk;
573     }
574 
575     /**
576      * Create the analysis cache object and register it for current execution thread.
577      * <p>
578      * This method is protected to allow clients override it and possibly reuse
579      * some previous analysis data (for Eclipse interactive re-build)
580      *
581      * @throws IOException
582      *             if error occurs registering analysis engines in a plugin
583      */
584     protected IAnalysisCache createAnalysisCache() throws IOException {
585         IAnalysisCache analysisCache = ClassFactory.instance().createAnalysisCache(classPath, bugReporter);
586 
587         // Register the "built-in" analysis engines
588         registerBuiltInAnalysisEngines(analysisCache);
589 
590         // Register analysis engines in plugins
591         registerPluginAnalysisEngines(detectorFactoryCollection, analysisCache);
592 
593         // Install the DetectorFactoryCollection as a database
594         analysisCache.eagerlyPutDatabase(DetectorFactoryCollection.class, detectorFactoryCollection);
595 
596         Global.setAnalysisCacheForCurrentThread(analysisCache);
597         return analysisCache;
598     }
599     /**
600      * Register the "built-in" analysis engines with given IAnalysisCache.
601      *
602      * @param analysisCache
603      *            an IAnalysisCache
604      */
605     public static void registerBuiltInAnalysisEngines(IAnalysisCache analysisCache) {
606         new edu.umd.cs.findbugs.classfile.engine.EngineRegistrar().registerAnalysisEngines(analysisCache);
607         new edu.umd.cs.findbugs.classfile.engine.asm.EngineRegistrar().registerAnalysisEngines(analysisCache);
608         new edu.umd.cs.findbugs.classfile.engine.bcel.EngineRegistrar().registerAnalysisEngines(analysisCache);
609     }
610 
611     /**
612      * Register all of the analysis engines defined in the plugins contained in
613      * a DetectorFactoryCollection with an IAnalysisCache.
614      *
615      * @param detectorFactoryCollection
616      *            a DetectorFactoryCollection
617      * @param analysisCache
618      *            an IAnalysisCache
619      * @throws IOException
620      */
621     public static void registerPluginAnalysisEngines(DetectorFactoryCollection detectorFactoryCollection,
622             IAnalysisCache analysisCache) throws IOException {
623         for (Iterator<Plugin> i = detectorFactoryCollection.pluginIterator(); i.hasNext();) {
624             Plugin plugin = i.next();
625 
626             Class<? extends IAnalysisEngineRegistrar> engineRegistrarClass = plugin.getEngineRegistrarClass();
627             if (engineRegistrarClass != null) {
628                 try {
629                     IAnalysisEngineRegistrar engineRegistrar = engineRegistrarClass.newInstance();
630                     engineRegistrar.registerAnalysisEngines(analysisCache);
631                 } catch (InstantiationException e) {
632                     IOException ioe = new IOException("Could not create analysis engine registrar for plugin "
633                             + plugin.getPluginId());
634                     ioe.initCause(e);
635                     throw ioe;
636                 } catch (IllegalAccessException e) {
637                     IOException ioe = new IOException("Could not create analysis engine registrar for plugin "
638                             + plugin.getPluginId());
639                     ioe.initCause(e);
640                     throw ioe;
641                 }
642             }
643         }
644     }
645 
646     /**
647      * Build the classpath from project codebases and system codebases.
648      *
649      * @throws InterruptedException
650      *             if the analysis thread is interrupted
651      * @throws IOException
652      *             if an I/O error occurs
653      * @throws CheckedAnalysisException
654      */
655     private void buildClassPath() throws InterruptedException, IOException, CheckedAnalysisException {
656         IClassPathBuilder builder = classFactory.createClassPathBuilder(bugReporter);
657 
658         {
659             HashSet<String> seen = new HashSet<>();
660             for (String path : project.getFileArray()) {
661                 if (seen.add(path)) {
662                     builder.addCodeBase(classFactory.createFilesystemCodeBaseLocator(path), true);
663                 }
664             }
665             for (String path : project.getAuxClasspathEntryList()) {
666                 if (seen.add(path)) {
667                     builder.addCodeBase(classFactory.createFilesystemCodeBaseLocator(path), false);
668                 }
669             }
670         }
671 
672         builder.scanNestedArchives(analysisOptions.scanNestedArchives);
673 
674         builder.build(classPath, progress);
675 
676         appClassList = builder.getAppClassList();
677 
678         if (PROGRESS) {
679             System.out.println(appClassList.size() + " classes scanned");
680         }
681 
682         // If any of the application codebases contain source code,
683         // add them to the source path.
684         // Also, use the last modified time of application codebases
685         // to set the project timestamp.
686         List<String> pathNames = new ArrayList<>();
687         for (Iterator<? extends ICodeBase> i = classPath.appCodeBaseIterator(); i.hasNext();) {
688             ICodeBase appCodeBase = i.next();
689 
690             if (appCodeBase.containsSourceFiles()) {
691                 String pathName = appCodeBase.getPathName();
692                 if (pathName != null) {
693                     pathNames.add(pathName);
694                 }
695             }
696 
697             project.addTimestamp(appCodeBase.getLastModifiedTime());
698         }
699 
700         project.addSourceDirs(pathNames);
701     }
702 
703     private void buildReferencedClassSet() throws InterruptedException {
704         // XXX: should drive progress dialog (scanning phase)?
705 
706         if (PROGRESS) {
707             System.out.println("Adding referenced classes");
708         }
709         Set<String> referencedPackageSet = new HashSet<>();
710 
711         LinkedList<ClassDescriptor> workList = new LinkedList<>();
712         workList.addAll(appClassList);
713 
714         Set<ClassDescriptor> seen = new HashSet<>();
715         Set<ClassDescriptor> appClassSet = new HashSet<>(appClassList);
716 
717         Set<ClassDescriptor> badAppClassSet = new HashSet<>();
718         HashSet<ClassDescriptor> knownDescriptors = new HashSet<>(DescriptorFactory.instance()
719                 .getAllClassDescriptors());
720         int count = 0;
721         Set<ClassDescriptor> addedToWorkList = new HashSet<>(appClassList);
722 
723         // add fields
724         //noinspection ConstantIfStatement
725         /*
726         if (false)
727             for (ClassDescriptor classDesc : appClassList) {
728                 try {
729                     XClass classNameAndInfo = Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc);
730                     for (XField f : classNameAndInfo.getXFields()) {
731                         String sig = f.getSignature();
732                         ClassDescriptor d = DescriptorFactory.createClassDescriptorFromFieldSignature(sig);
733                         if (d != null && addedToWorkList.add(d))
734                             workList.addLast(d);
735                     }
736                 } catch (RuntimeException e) {
737                     bugReporter.logError("Error scanning " + classDesc + " for referenced classes", e);
738                     if (appClassSet.contains(classDesc)) {
739                         badAppClassSet.add(classDesc);
740                     }
741                 } catch (MissingClassException e) {
742                     // Just log it as a missing class
743                     bugReporter.reportMissingClass(e.getClassDescriptor());
744                     if (appClassSet.contains(classDesc)) {
745                         badAppClassSet.add(classDesc);
746                     }
747                 }
748             }
749          */
750         while (!workList.isEmpty()) {
751             if (Thread.interrupted()) {
752                 throw new InterruptedException();
753             }
754             ClassDescriptor classDesc = workList.removeFirst();
755 
756             if (seen.contains(classDesc)) {
757                 continue;
758             }
759             seen.add(classDesc);
760 
761             if (!knownDescriptors.contains(classDesc)) {
762                 count++;
763                 if (PROGRESS && count % 5000 == 0) {
764                     System.out.println("Adding referenced class " + classDesc);
765                 }
766             }
767 
768             referencedPackageSet.add(classDesc.getPackageName());
769 
770             // Get list of referenced classes and add them to set.
771             // Add superclasses and superinterfaces to worklist.
772             try {
773                 XClass classNameAndInfo = Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc);
774 
775                 ClassDescriptor superclassDescriptor = classNameAndInfo.getSuperclassDescriptor();
776                 if (superclassDescriptor != null && addedToWorkList.add(superclassDescriptor)) {
777                     workList.addLast(superclassDescriptor);
778                 }
779 
780                 for (ClassDescriptor ifaceDesc : classNameAndInfo.getInterfaceDescriptorList()) {
781                     if (addedToWorkList.add(ifaceDesc)) {
782                         workList.addLast(ifaceDesc);
783                     }
784                 }
785 
786                 ClassDescriptor enclosingClass = classNameAndInfo.getImmediateEnclosingClass();
787                 if (enclosingClass != null && addedToWorkList.add(enclosingClass)) {
788                     workList.addLast(enclosingClass);
789                 }
790 
791             } catch (RuntimeException e) {
792                 bugReporter.logError("Error scanning " + classDesc + " for referenced classes", e);
793                 if (appClassSet.contains(classDesc)) {
794                     badAppClassSet.add(classDesc);
795                 }
796             } catch (MissingClassException e) {
797                 // Just log it as a missing class
798                 bugReporter.reportMissingClass(e.getClassDescriptor());
799                 if (appClassSet.contains(classDesc)) {
800                     badAppClassSet.add(classDesc);
801                 }
802             } catch (CheckedAnalysisException e) {
803                 // Failed to scan a referenced class --- just log the error and
804                 // continue
805                 bugReporter.logError("Error scanning " + classDesc + " for referenced classes", e);
806                 if (appClassSet.contains(classDesc)) {
807                     badAppClassSet.add(classDesc);
808                 }
809             }
810         }
811         // Delete any application classes that could not be read
812         appClassList.removeAll(badAppClassSet);
813         DescriptorFactory.instance().purge(badAppClassSet);
814 
815         for (ClassDescriptor d : DescriptorFactory.instance().getAllClassDescriptors()) {
816             referencedPackageSet.add(d.getPackageName());
817         }
818         referencedClassSet = new ArrayList<>(DescriptorFactory.instance().getAllClassDescriptors());
819 
820         // Based on referenced packages, add any resolvable package-info classes
821         // to the set of referenced classes.
822         if (PROGRESS) {
823             referencedPackageSet.remove("");
824             System.out.println("Added " + count + " referenced classes");
825             System.out.println("Total of " + referencedPackageSet.size() + " packages");
826             for (ClassDescriptor d : referencedClassSet) {
827                 System.out.println("  " + d);
828             }
829         }
830     }
831 
832     public List<ClassDescriptor> sortByCallGraph(Collection<ClassDescriptor> classList, OutEdges<ClassDescriptor> outEdges) {
833         List<ClassDescriptor> evaluationOrder = edu.umd.cs.findbugs.util.TopologicalSort.sortByCallGraph(classList, outEdges);
834         edu.umd.cs.findbugs.util.TopologicalSort.countBadEdges(evaluationOrder, outEdges);
835         return evaluationOrder;
836 
837     }
838 
839     public static void clearAnalysisContext() {
840         AnalysisContext.removeCurrentAnalysisContext();
841     }
842 
843     /**
844      * Create the AnalysisContext that will serve as the BCEL-compatibility
845      * layer over the AnalysisCache.
846      *
847      * @param project
848      *            The project
849      * @param appClassList
850      *            list of ClassDescriptors identifying application classes
851      * @param sourceInfoFileName
852      *            name of source info file (null if none)
853      */
854     public static void createAnalysisContext(Project project, List<ClassDescriptor> appClassList,
855             @CheckForNull String sourceInfoFileName) throws  IOException {
856         AnalysisContext analysisContext = new AnalysisContext(project);
857 
858         // Make this the current analysis context
859         AnalysisContext.setCurrentAnalysisContext(analysisContext);
860 
861         // Make the AnalysisCache the backing store for
862         // the BCEL Repository
863         analysisContext.clearRepository();
864 
865         // If needed, load SourceInfoMap
866         if (sourceInfoFileName != null) {
867             SourceInfoMap sourceInfoMap = analysisContext.getSourceInfoMap();
868             sourceInfoMap.read(new FileInputStream(sourceInfoFileName));
869         }
870     }
871 
872     public static void setAppClassList(List<ClassDescriptor> appClassList)  {
873         AnalysisContext analysisContext = AnalysisContext
874                 .currentAnalysisContext();
875 
876         analysisContext.setAppClassList(appClassList);
877     }
878 
879     /**
880      * Configure analysis feature settings.
881      */
882     private void configureAnalysisFeatures() {
883         for (AnalysisFeatureSetting setting : analysisOptions.analysisFeatureSettingList) {
884             setting.configure(AnalysisContext.currentAnalysisContext());
885         }
886         AnalysisContext.currentAnalysisContext().setBoolProperty(AnalysisFeatures.MERGE_SIMILAR_WARNINGS,
887                 analysisOptions.mergeSimilarWarnings);
888     }
889 
890     /**
891      * Create an execution plan.
892      *
893      * @throws OrderingConstraintException
894      *             if the detector ordering constraints are inconsistent
895      */
896     private void createExecutionPlan() throws OrderingConstraintException {
897         executionPlan = new ExecutionPlan();
898 
899         // Use user preferences to decide which detectors are enabled.
900         DetectorFactoryChooser detectorFactoryChooser = new DetectorFactoryChooser() {
901             HashSet<DetectorFactory> forcedEnabled = new HashSet<>();
902 
903             @Override
904             public boolean choose(DetectorFactory factory) {
905                 boolean result = FindBugs.isDetectorEnabled(FindBugs2.this, factory, rankThreshold) || forcedEnabled.contains(factory);
906                 if (ExecutionPlan.DEBUG) {
907                     System.out.printf("  %6s %s %n", result, factory.getShortName());
908                 }
909                 return result;
910             }
911 
912             @Override
913             public void enable(DetectorFactory factory) {
914                 forcedEnabled.add(factory);
915                 factory.setEnabledButNonReporting(true);
916             }
917 
918         };
919         executionPlan.setDetectorFactoryChooser(detectorFactoryChooser);
920 
921         if (ExecutionPlan.DEBUG) {
922             System.out.println("rank threshold is " + rankThreshold);
923         }
924         // Add plugins
925         for (Iterator<Plugin> i = detectorFactoryCollection.pluginIterator(); i.hasNext();) {
926             Plugin plugin = i.next();
927             if (DEBUG) {
928                 System.out.println("Adding plugin " + plugin.getPluginId() + " to execution plan");
929             }
930             executionPlan.addPlugin(plugin);
931         }
932 
933         // Build the execution plan
934         executionPlan.build();
935 
936         // Stash the ExecutionPlan in the AnalysisCache.
937         Global.getAnalysisCache().eagerlyPutDatabase(ExecutionPlan.class, executionPlan);
938 
939         if (PROGRESS) {
940             System.out.println(executionPlan.getNumPasses() + " passes in execution plan");
941         }
942     }
943 
944     /**
945      * Analyze the classes in the application codebase.
946      */
947     private void analyzeApplication() throws InterruptedException {
948         int passCount = 0;
949         Profiler profiler = bugReporter.getProjectStats().getProfiler();
950         profiler.start(this.getClass());
951         AnalysisContext.currentXFactory().canonicalizeAll();
952         try {
953             boolean multiplePasses = executionPlan.getNumPasses() > 1;
954             if (executionPlan.getNumPasses() == 0) {
955                 throw new AssertionError("no analysis passes");
956             }
957             int[] classesPerPass = new int[executionPlan.getNumPasses()];
958             classesPerPass[0] = referencedClassSet.size();
959             for (int i = 0; i < classesPerPass.length; i++) {
960                 classesPerPass[i] = i == 0 ? referencedClassSet.size() : appClassList.size();
961             }
962             progress.predictPassCount(classesPerPass);
963             XFactory factory = AnalysisContext.currentXFactory();
964             Collection<ClassDescriptor> badClasses = new LinkedList<>();
965             for (ClassDescriptor desc : referencedClassSet) {
966                 try {
967                     XClass info = Global.getAnalysisCache().getClassAnalysis(XClass.class, desc);
968                     factory.intern(info);
969                 } catch (CheckedAnalysisException e) {
970                     AnalysisContext.logError("Couldn't get class info for " + desc, e);
971                     badClasses.add(desc);
972                 } catch (RuntimeException e) {
973                     AnalysisContext.logError("Couldn't get class info for " + desc, e);
974                     badClasses.add(desc);
975                 }
976             }
977             if (!badClasses.isEmpty()) {
978                 referencedClassSet = new LinkedHashSet<>(referencedClassSet);
979                 referencedClassSet.removeAll(badClasses);
980             }
981 
982             long startTime = System.currentTimeMillis();
983             bugReporter.getProjectStats().setReferencedClasses(referencedClassSet.size());
984             for (Iterator<AnalysisPass> passIterator = executionPlan.passIterator(); passIterator.hasNext();) {
985                 AnalysisPass pass = passIterator.next();
986                 // The first pass is generally a non-reporting pass which
987                 // gathers information about referenced classes.
988                 boolean isNonReportingFirstPass = multiplePasses && passCount == 0;
989 
990                 // Instantiate the detectors
991                 Detector2[] detectorList = pass.instantiateDetector2sInPass(bugReporter);
992 
993                 // If there are multiple passes, then on the first pass,
994                 // we apply detectors to all classes referenced by the
995                 // application classes.
996                 // On subsequent passes, we apply detector only to application
997                 // classes.
998                 Collection<ClassDescriptor> classCollection = (isNonReportingFirstPass) ? referencedClassSet : appClassList;
999                 AnalysisContext.currentXFactory().canonicalizeAll();
1000                 if (PROGRESS || LIST_ORDER) {
1001                     System.out.printf("%6d : Pass %d: %d classes%n", (System.currentTimeMillis() - startTime)/1000, passCount,  classCollection.size());
1002                     if (DEBUG) {
1003                         XFactory.profile();
1004                     }
1005                 }
1006                 if (!isNonReportingFirstPass) {
1007                     OutEdges<ClassDescriptor> outEdges = e -> {
1008                         try {
1009                             XClass classNameAndInfo = Global.getAnalysisCache().getClassAnalysis(XClass.class, e);
1010                             return classNameAndInfo.getCalledClassDescriptors();
1011                         } catch (CheckedAnalysisException e2) {
1012                             AnalysisContext.logError("error while analyzing " + e.getClassName(), e2);
1013                             return Collections.emptyList();
1014 
1015                         }
1016                     };
1017 
1018                     classCollection = sortByCallGraph(classCollection, outEdges);
1019                 }
1020                 if (LIST_ORDER) {
1021                     System.out.println("Analysis order:");
1022                     for (ClassDescriptor c : classCollection) {
1023                         System.out.println("  " + c);
1024                     }
1025                 }
1026                 AnalysisContext currentAnalysisContext = AnalysisContext.currentAnalysisContext();
1027                 currentAnalysisContext.updateDatabases(passCount);
1028 
1029                 progress.startAnalysis(classCollection.size());
1030                 int count = 0;
1031                 Global.getAnalysisCache().purgeAllMethodAnalysis();
1032                 Global.getAnalysisCache().purgeClassAnalysis(FBClassReader.class);
1033                 for (ClassDescriptor classDescriptor : classCollection) {
1034                     long classStartNanoTime = 0;
1035                     if (PROGRESS) {
1036                         classStartNanoTime = System.nanoTime();
1037                         System.out.printf("%6d %d/%d  %d/%d %s%n", (System.currentTimeMillis() - startTime)/1000,
1038                                 passCount, executionPlan.getNumPasses(), count,
1039                                 classCollection.size(), classDescriptor);
1040                     }
1041                     count++;
1042 
1043                     // Check to see if class is excluded by the class screener.
1044                     // In general, we do not want to screen classes from the
1045                     // first pass, even if they would otherwise be excluded.
1046                     if ((SCREEN_FIRST_PASS_CLASSES || !isNonReportingFirstPass)
1047                             && !classScreener.matches(classDescriptor.toResourceName())) {
1048                         if (DEBUG) {
1049                             System.out.println("*** Excluded by class screener");
1050                         }
1051                         continue;
1052                     }
1053                     boolean isHuge = currentAnalysisContext.isTooBig(classDescriptor);
1054                     if (isHuge && currentAnalysisContext.isApplicationClass(classDescriptor)) {
1055                         bugReporter.reportBug(new BugInstance("SKIPPED_CLASS_TOO_BIG", Priorities.NORMAL_PRIORITY)
1056                         .addClass(classDescriptor));
1057                     }
1058                     currentClassName = ClassName.toDottedClassName(classDescriptor.getClassName());
1059                     notifyClassObservers(classDescriptor);
1060                     profiler.startContext(currentClassName);
1061                     currentAnalysisContext.setClassBeingAnalyzed(classDescriptor);
1062 
1063                     try {
1064                         for (Detector2 detector : detectorList) {
1065                             if (Thread.interrupted()) {
1066                                 throw new InterruptedException();
1067                             }
1068                             if (isHuge && !FirstPassDetector.class.isAssignableFrom(detector.getClass())) {
1069                                 continue;
1070                             }
1071                             if (DEBUG) {
1072                                 System.out.println("Applying " + detector.getDetectorClassName() + " to " + classDescriptor);
1073                                 // System.out.println("foo: " +
1074                                 // NonReportingDetector.class.isAssignableFrom(detector.getClass())
1075                                 // + ", bar: " + detector.getClass().getName());
1076                             }
1077                             try {
1078                                 profiler.start(detector.getClass());
1079                                 detector.visitClass(classDescriptor);
1080                             } catch (ClassFormatException e) {
1081                                 logRecoverableException(classDescriptor, detector, e);
1082                             } catch (MissingClassException e) {
1083                                 Global.getAnalysisCache().getErrorLogger().reportMissingClass(e.getClassDescriptor());
1084                             } catch (CheckedAnalysisException e) {
1085                                 logRecoverableException(classDescriptor, detector, e);
1086                             } catch (RuntimeException e) {
1087                                 logRecoverableException(classDescriptor, detector, e);
1088                             } finally {
1089                                 profiler.end(detector.getClass());
1090                             }
1091                         }
1092                     } finally {
1093 
1094                         progress.finishClass();
1095                         profiler.endContext(currentClassName);
1096                         currentAnalysisContext.clearClassBeingAnalyzed();
1097                         if (PROGRESS) {
1098                             long usecs = (System.nanoTime() - classStartNanoTime)/1000;
1099                             if (usecs > 15000) {
1100                                 int classSize = currentAnalysisContext.getClassSize(classDescriptor);
1101                                 long speed = usecs /classSize;
1102                                 if (speed > 15) {
1103                                     System.out.printf("  %6d usecs/byte  %6d msec  %6d bytes  %d pass %s%n", speed, usecs/1000, classSize, passCount,
1104                                             classDescriptor);
1105                                 }
1106                             }
1107 
1108                         }
1109                     }
1110                 }
1111 
1112                 // Call finishPass on each detector
1113                 for (Detector2 detector : detectorList) {
1114                     detector.finishPass();
1115                 }
1116 
1117                 progress.finishPerClassAnalysis();
1118 
1119                 passCount++;
1120             }
1121 
1122 
1123         } finally {
1124 
1125             bugReporter.finish();
1126             bugReporter.reportQueuedErrors();
1127             profiler.end(this.getClass());
1128             if (PROGRESS) {
1129                 System.out.println("Analysis completed");
1130             }
1131         }
1132 
1133     }
1134 
1135     /**
1136      * Notify all IClassObservers that we are visiting given class.
1137      *
1138      * @param classDescriptor
1139      *            the class being visited
1140      */
1141     private void notifyClassObservers(ClassDescriptor classDescriptor) {
1142         for (IClassObserver observer : classObserverList) {
1143             observer.observeClass(classDescriptor);
1144         }
1145     }
1146 
1147     /**
1148      * Report an exception that occurred while analyzing a class with a
1149      * detector.
1150      *
1151      * @param classDescriptor
1152      *            class being analyzed
1153      * @param detector
1154      *            detector doing the analysis
1155      * @param e
1156      *            the exception
1157      */
1158     private void logRecoverableException(ClassDescriptor classDescriptor, Detector2 detector, Throwable e) {
1159         bugReporter.logError(
1160                 "Exception analyzing " + classDescriptor.toDottedClassName() + " using detector "
1161                         + detector.getDetectorClassName(), e);
1162     }
1163 
1164     public static void main(String[] args) throws Exception {
1165         // Sanity-check the loaded BCEL classes
1166         if (!CheckBcel.check()) {
1167             System.exit(1);
1168         }
1169 
1170         // Create FindBugs2 engine
1171         FindBugs2 findBugs = new FindBugs2();
1172 
1173         // Parse command line and configure the engine
1174         TextUICommandLine commandLine = new TextUICommandLine();
1175         FindBugs.processCommandLine(commandLine, args, findBugs);
1176 
1177 
1178         boolean justPrintConfiguration = commandLine.justPrintConfiguration();
1179         if (justPrintConfiguration || commandLine.justPrintVersion()) {
1180             Version.printVersion(justPrintConfiguration);
1181 
1182             return;
1183         }
1184         // Away we go!
1185 
1186 
1187         FindBugs.runMain(findBugs, commandLine);
1188 
1189     }
1190 
1191 
1192     @Override
1193     public void setAbridgedMessages(boolean xmlWithAbridgedMessages) {
1194         analysisOptions.abridgedMessages = xmlWithAbridgedMessages;
1195     }
1196 
1197     @Override
1198     public void setMergeSimilarWarnings(boolean mergeSimilarWarnings) {
1199         this.analysisOptions.mergeSimilarWarnings = mergeSimilarWarnings;
1200     }
1201 
1202     @Override
1203     public void setApplySuppression(boolean applySuppression) {
1204         this.analysisOptions.applySuppression = applySuppression;
1205     }
1206 
1207     @Override
1208     public void setRankThreshold(int rankThreshold) {
1209         this.rankThreshold = rankThreshold;
1210     }
1211 
1212     @Override
1213     public void finishSettings() {
1214         if (analysisOptions.applySuppression) {
1215             bugReporter = new FilterBugReporter(bugReporter, getProject().getSuppressionFilter(), false);
1216         }
1217     }
1218 
1219     @Nonnull
1220     Set<String> explicitlyEnabledBugReporterDecorators = Collections.emptySet();
1221 
1222     @Nonnull
1223     Set<String> explicitlyDisabledBugReporterDecorators = Collections.emptySet();
1224 
1225     @Override
1226     public void setBugReporterDecorators(Set<String> explicitlyEnabled, Set<String> explicitlyDisabled) {
1227         explicitlyEnabledBugReporterDecorators = explicitlyEnabled;
1228         explicitlyDisabledBugReporterDecorators = explicitlyDisabled;
1229     }
1230 
1231 }