/** * The DetectorFactoryCollection stores all of the DetectorFactory objects used to create the * Detectors which implement the various analyses. It is a singleton class. * * @author David Hovemeyer * @see DetectorFactory */ public class DetectorFactoryCollection implements UpdateCheckCallback { private static final Logger LOGGER = Logger.getLogger(DetectorFactoryCollection.class.getName()); private static final boolean DEBUG_JAWS = SystemProperties.getBoolean("findbugs.jaws.debug"); // private static final boolean DEBUG = Boolean.getBoolean("dfc.debug"); private static DetectorFactoryCollection theInstance; private static final Object lock = new Object(); private final Map<String, Plugin> pluginByIdMap = new LinkedHashMap<String, Plugin>(); private Plugin corePlugin; private final List<DetectorFactory> factoryList = new ArrayList<DetectorFactory>(); private final Map<String, DetectorFactory> factoriesByName = new HashMap<String, DetectorFactory>(); private final Map<String, DetectorFactory> factoriesByDetectorClassName = new HashMap<String, DetectorFactory>(); private final Map<String, CloudPlugin> registeredClouds = new LinkedHashMap<String, CloudPlugin>(); protected final Map<String, BugCategory> categoryDescriptionMap = new HashMap<String, BugCategory>(); protected final Map<String, BugPattern> bugPatternMap = new HashMap<String, BugPattern>(); protected final Map<String, BugCode> bugCodeMap = new HashMap<String, BugCode>(); private final UpdateChecker updateChecker; private final CopyOnWriteArrayList<PluginUpdateListener> pluginUpdateListeners = new CopyOnWriteArrayList<PluginUpdateListener>(); private volatile List<UpdateChecker.PluginUpdate> updates; private boolean updatesForced; private final Collection<Plugin> pluginsToUpdate; final Map<String, String> globalOptions = new HashMap<String, String>(); final Map<String, Plugin> globalOptionsSetter = new HashMap<String, Plugin>(); protected DetectorFactoryCollection() { this(true, false, Plugin.getAllPlugins(), new ArrayList<Plugin>()); } protected DetectorFactoryCollection(Plugin onlyPlugin) { this(false, true, Collections.singleton(onlyPlugin), new ArrayList<Plugin>()); } protected DetectorFactoryCollection(Collection<Plugin> enabled) { this(true, true, enabled, enabled); } private DetectorFactoryCollection( boolean loadCore, boolean forceLoad, @Nonnull Collection<Plugin> pluginsToLoad, @Nonnull Collection<Plugin> enabledPlugins) { if (loadCore) { loadCorePlugin(); } for (Plugin plugin : pluginsToLoad) { if (forceLoad || plugin.isGloballyEnabled() && !plugin.isCorePlugin()) { loadPlugin(plugin); if (!enabledPlugins.contains(plugin)) { enabledPlugins.add(plugin); } } } setGlobalOptions(); updateChecker = new UpdateChecker(this); pluginsToUpdate = combine(enabledPlugins); // There are too many places where the detector factory collection can be created, // and in many cases it has nothing to do with update checks, like Plugin#addCustomPlugin(URI). // Line below commented to allow custom plugins be loaded/registered BEFORE we // start to update something. The reason is that custom plugins // can disable update globally OR just will be NOT INCLUDED in the update check! // updateChecker.checkForUpdates(pluginsToUpdate, false); } /** * @param force whether the updates should be shown to the user no matter what - even if the * updates have been seen before */ public void checkForUpdates(boolean force) { updateChecker.checkForUpdates(pluginsToUpdate, force); } private Collection<Plugin> combine(Collection<Plugin> enabled) { List<Plugin> result = new ArrayList<Plugin>(enabled); if (corePlugin != null && !result.contains(corePlugin)) { result.add(corePlugin); } return result; } /** * Reset the factory singleton. * * <p><b>Implementation note:</b> This method is public for tests only! * * @param instance use null to clear the instance */ public static void resetInstance(@CheckForNull DetectorFactoryCollection instance) { synchronized (lock) { theInstance = instance; } } /** * Get the single instance of DetectorFactoryCollection, which knows each and every loaded plugin, * independently of it's enablement */ public static DetectorFactoryCollection instance() { synchronized (lock) { if (theInstance == null) { theInstance = new DetectorFactoryCollection(); } return theInstance; } } private void setGlobalOptions() { globalOptions.clear(); globalOptionsSetter.clear(); for (Plugin p : plugins()) { if (p.isGloballyEnabled()) { for (Map.Entry<String, String> e : p.getMyGlobalOptions().entrySet()) { String key = e.getKey(); String value = e.getValue(); String oldValue = globalOptions.get(key); if (oldValue != null) { if (oldValue.equals(value)) { continue; } Plugin oldP = globalOptionsSetter.get(key); throw new RuntimeException( "Incompatible global options for " + key + "; conflict between " + oldP.getPluginId() + " and " + p.getPluginId()); } globalOptions.put(key, value); globalOptionsSetter.put(key, p); } } } } @Override public @CheckForNull String getGlobalOption(String key) { return globalOptions.get(key); } @Override public @CheckForNull Plugin getGlobalOptionSetter(String key) { return globalOptionsSetter.get(key); } /** Return an Iterator over all available Plugin objects. */ public Iterator<Plugin> pluginIterator() { return pluginByIdMap.values().iterator(); } /** Return an Collection of all available Plugin objects. */ public Collection<Plugin> plugins() { return pluginByIdMap.values(); } @Nonnull public Plugin getCorePlugin() { if (corePlugin == null) { throw new IllegalStateException("No core plugin"); } return corePlugin; } /** * Get a Plugin by its unique id. * * @param pluginId the unique id * @return the Plugin with that id, or null if no such Plugin is found */ public Plugin getPluginById(String pluginId) { return pluginByIdMap.get(pluginId); } /** Return an Iterator over the DetectorFactory objects for all registered Detectors. */ public Iterator<DetectorFactory> factoryIterator() { return factoryList.iterator(); } /** Return an Iterable over the DetectorFactory objects for all registered Detectors. */ public Iterable<DetectorFactory> getFactories() { return factoryList; } public boolean isDisabledByDefault(String bugPatternOrCode) { @CheckForNull BugPattern pattern = lookupBugPattern(bugPatternOrCode); if (pattern != null) { for (DetectorFactory fac : factoryList) { if (fac.isDefaultEnabled() && fac.getReportedBugPatterns().contains(pattern)) { return false; } } return true; } @CheckForNull BugCode code = lookupBugCode(bugPatternOrCode); if (code != null) { for (DetectorFactory fac : factoryList) { if (fac.isDefaultEnabled()) { for (BugPattern p : fac.getReportedBugPatterns()) { if (p.getBugCode().equals(code)) { return false; } } } } return true; } return false; } /** * Look up a DetectorFactory by its short name. * * @param name the short name * @return the DetectorFactory, or null if there is no factory with that short name */ public DetectorFactory getFactory(String name) { return factoriesByName.get(name); } /** * Look up a DetectorFactory by its class name. * * @param className the class name * @return the DetectoryFactory, or null if there is no factory with that class name */ public DetectorFactory getFactoryByClassName(String className) { return factoriesByDetectorClassName.get(className); } /** Register a DetectorFactory. */ void registerDetector(DetectorFactory factory) { if (FindBugs.DEBUG) { System.out.println("Registering detector: " + factory.getFullName()); } String detectorName = factory.getShortName(); if (!factoryList.contains(factory)) { factoryList.add(factory); } else { LOGGER.log( Level.WARNING, "Trying to add already registered factory: " + factory + ", " + factory.getPlugin()); } factoriesByName.put(detectorName, factory); factoriesByDetectorClassName.put(factory.getFullName(), factory); } void unRegisterDetector(DetectorFactory factory) { if (FindBugs.DEBUG) { System.out.println("Unregistering detector: " + factory.getFullName()); } String detectorName = factory.getShortName(); factoryList.remove(factory); factoriesByName.remove(detectorName); factoriesByDetectorClassName.remove(factory.getFullName()); } /** * See if the location of ${findbugs.home} can be inferred from the location of findbugs.jar in * the classpath. * * @return inferred ${findbugs.home}, or null if we can't figure it out */ private static String inferFindBugsHome() { Pattern[] findbugsJarNames = { Pattern.compile("findbugs\\.jar$"), }; for (Pattern jarNamePattern : findbugsJarNames) { String findbugsJarCodeBase = ClassPathUtil.findCodeBaseInClassPath( jarNamePattern, SystemProperties.getProperty("java.class.path")); if (findbugsJarCodeBase != null) { File findbugsJar = new File(findbugsJarCodeBase); File libDir = findbugsJar.getParentFile(); if ("lib".equals(libDir.getName())) { String fbHome = libDir.getParent(); FindBugs.setHome(fbHome); return fbHome; } } } String classFilePath = FindBugs.class.getName().replaceAll("\\.", "/") + ".class"; URL resource = FindBugs.class.getClassLoader().getResource(classFilePath); if (resource != null && "file".equals(resource.getProtocol())) { try { String classfile = URLDecoder.decode(resource.getPath(), Charset.defaultCharset().name()); Matcher m = Pattern.compile("(.*)/.*?/edu/umd.*").matcher(classfile); if (m.matches()) { String home = m.group(1); if (new File(home + "/etc/findbugs.xml").exists()) { FindBugs.setHome(home); return home; } } } catch (UnsupportedEncodingException e) { } } return null; } public static String getFindBugsHome() { String homeDir = FindBugs.getHome(); if (homeDir == null) { // Attempt to infer findbugs.home from the observed // location of findbugs.jar. homeDir = inferFindBugsHome(); } return homeDir; } @CheckForNull public static URL getCoreResource(String name) { return PluginLoader.getCoreResource(name); } private void loadCorePlugin() { Plugin plugin = PluginLoader.getCorePluginLoader().getPlugin(); loadPlugin(plugin); corePlugin = plugin; } public static void jawsDebugMessage(String message) { if (DEBUG_JAWS) { JOptionPane.showMessageDialog(null, message); } else if (FindBugs.DEBUG) { System.err.println(message); } } void loadPlugin(Plugin plugin) { if (FindBugs.DEBUG) { System.out.println("Loading " + plugin.getPluginId()); } pluginByIdMap.put(plugin.getPluginId(), plugin); setGlobalOptions(); // Register all of the detectors that this plugin contains for (DetectorFactory factory : plugin.getDetectorFactories()) { registerDetector(factory); } for (BugCategory bugCategory : plugin.getBugCategories()) { registerBugCategory(bugCategory); } // Register the BugPatterns for (BugPattern bugPattern : plugin.getBugPatterns()) { registerBugPattern(bugPattern); } // Register the BugCodes for (BugCode bugCode : plugin.getBugCodes()) { registerBugCode(bugCode); } for (CloudPlugin cloud : plugin.getCloudPlugins()) { registerCloud(cloud); } } void unLoadPlugin(Plugin plugin) { pluginByIdMap.remove(plugin.getPluginId()); setGlobalOptions(); for (DetectorFactory factory : plugin.getDetectorFactories()) { unRegisterDetector(factory); } for (BugCategory bugCategory : plugin.getBugCategories()) { unRegisterBugCategory(bugCategory); } for (BugPattern bugPattern : plugin.getBugPatterns()) { unRegisterBugPattern(bugPattern); } for (BugCode bugCode : plugin.getBugCodes()) { unRegisterBugCode(bugCode); } for (CloudPlugin cloud : plugin.getCloudPlugins()) { unRegisterCloud(cloud); } } @Override public void pluginUpdateCheckComplete( List<UpdateChecker.PluginUpdate> newUpdates, boolean force) { this.updates = newUpdates; this.updatesForced = force; for (PluginUpdateListener listener : pluginUpdateListeners) { try { listener.pluginUpdateCheckComplete(newUpdates, force); } catch (Throwable e) { LOGGER.log(Level.INFO, "Error during update check callback", e); } } } public void addPluginUpdateListener(PluginUpdateListener listener) { pluginUpdateListeners.add(listener); if (updates != null) { listener.pluginUpdateCheckComplete(updates, updatesForced); } else if (!updateChecker.updateChecksGloballyDisabled()) { checkForUpdates(false); } } public FutureValue<Collection<UpdateChecker.PluginUpdate>> getUpdates() { final FutureValue<Collection<UpdateChecker.PluginUpdate>> results = new FutureValue<Collection<UpdateChecker.PluginUpdate>>(); addPluginUpdateListener( new PluginUpdateListener() { @Override public void pluginUpdateCheckComplete( Collection<UpdateChecker.PluginUpdate> u, boolean force) { results.set(u); } }); return results; } public Map<String, CloudPlugin> getRegisteredClouds() { return Collections.unmodifiableMap(registeredClouds); } void registerCloud(CloudPlugin cloudPlugin) { LOGGER.log(Level.FINE, "Registering " + cloudPlugin.getId()); registeredClouds.put(cloudPlugin.getId(), cloudPlugin); } void unRegisterCloud(CloudPlugin cloudPlugin) { LOGGER.log(Level.FINE, "Unregistering " + cloudPlugin.getId()); registeredClouds.remove(cloudPlugin.getId()); } /** * Set the metadata for a bug category. If the category's metadata has already been set, this does * nothing. * * @param bc the BugCategory object holding the metadata for the category * @return false if the category's metadata has already been set, true otherwise */ public boolean registerBugCategory(BugCategory bc) { String category = bc.getCategory(); if (categoryDescriptionMap.get(category) != null) { return false; } categoryDescriptionMap.put(category, bc); return true; } protected boolean unRegisterBugCategory(BugCategory bc) { String category = bc.getCategory(); categoryDescriptionMap.remove(category); return true; } /** * Register a BugPattern. * * @param bugPattern the BugPattern */ public void registerBugPattern(BugPattern bugPattern) { bugPatternMap.put(bugPattern.getType(), bugPattern); } protected void unRegisterBugPattern(BugPattern bugPattern) { bugPatternMap.remove(bugPattern.getType()); } /** Get an Iterator over all registered bug patterns. */ public Iterator<BugPattern> bugPatternIterator() { return bugPatternMap.values().iterator(); } /** Get an Iterator over all registered bug patterns. */ public Collection<BugPattern> getBugPatterns() { return bugPatternMap.values(); } /** * Look up bug pattern. * * @param bugType the bug type for the bug pattern * @return the BugPattern, or null if it can't be found */ public @CheckForNull BugPattern lookupBugPattern(String bugType) { if (bugType == null) { return null; } return bugPatternMap.get(bugType); } public void registerBugCode(BugCode bugCode) { bugCodeMap.put(bugCode.getAbbrev(), bugCode); } protected void unRegisterBugCode(BugCode bugCode) { bugCodeMap.remove(bugCode.getAbbrev()); } public Collection<BugCode> getBugCodes() { return bugCodeMap.values(); } /** * Get a description for given "bug type". FIXME: this is referred to elsewhere as the "bug code" * or "bug abbrev". Should make the terminology consistent everywhere. In this case, the bug type * refers to the short prefix code prepended to the long and short bug messages. * * @param shortBugType the short bug type code * @return the description of that short bug type code means */ public @Nonnull BugCode getBugCode(String shortBugType) { BugCode bugCode = lookupBugCode(shortBugType); if (bugCode == null) { throw new IllegalArgumentException("Error: missing bug code for key" + shortBugType); } return bugCode; } /** * @param shortBugType the short bug type code * @return the description of that short bug type code means */ public @CheckForNull BugCode lookupBugCode(String shortBugType) { return bugCodeMap.get(shortBugType); } /** * Get the BugCategory object for a category key. Returns null if no BugCategory object can be * found. * * @param category the category key * @return the BugCategory object (may be null) */ public BugCategory getBugCategory(String category) { return categoryDescriptionMap.get(category); } /** * Get a Collection containing all known bug category keys. E.g., "CORRECTNESS", "MT_CORRECTNESS", * "PERFORMANCE", etc. * * <p>Excludes hidden bug categories * * @return Collection of bug category keys. */ public Collection<String> getBugCategories() { ArrayList<String> result = new ArrayList<String>(categoryDescriptionMap.size()); for (BugCategory c : categoryDescriptionMap.values()) { if (!c.isHidden()) { result.add(c.getCategory()); } } return result; } public Collection<BugCategory> getBugCategoryObjects() { return categoryDescriptionMap.values(); // backed by the Map } public UpdateChecker getUpdateChecker() { return updateChecker; } }
public class DeepSubtypeAnalysis { private static JavaClass serializable; private static JavaClass collection; private static JavaClass comparator; private static JavaClass map; private static JavaClass remote; private static ClassNotFoundException storedException; private static final boolean DEBUG = SystemProperties.getBoolean("dsa.debug"); static { try { serializable = AnalysisContext.lookupSystemClass("java.io.Serializable"); collection = AnalysisContext.lookupSystemClass("java.util.Collection"); map = AnalysisContext.lookupSystemClass("java.util.Map"); comparator = AnalysisContext.lookupSystemClass("java.util.Comparator"); } catch (ClassNotFoundException e) { storedException = e; } try { remote = AnalysisContext.lookupSystemClass("java.rmi.Remote"); } catch (ClassNotFoundException e) { if (storedException == null) storedException = e; } } private static boolean containsConcreteClasses(Set<JavaClass> s) { for (JavaClass c : s) if (!c.isInterface() && !c.isAbstract()) return true; return false; } public static double isDeepSerializable(ReferenceType type) throws ClassNotFoundException { return isDeepSerializable(type.getSignature()); } public static double isDeepSerializable(@DottedClassName String refSig) throws ClassNotFoundException { if (storedException != null) throw storedException; if (isPrimitiveComponentClass(refSig)) { if (DEBUG) { System.out.println("regSig \"" + refSig + "\" is primitive component class"); } return 1.0; } String refName = getComponentClass(refSig); if (refName.equals("java.lang.Object")) return 0.99; JavaClass refJavaClass = Repository.lookupClass(refName); return isDeepSerializable(refJavaClass); } public static double isDeepRemote(ReferenceType refType) { return isDeepRemote(refType.getSignature()); } public static double isDeepRemote(String refSig) { if (remote == null) return 0.1; String refName = getComponentClass(refSig); if (refName.equals("java.lang.Object")) return 0.99; JavaClass refJavaClass; try { refJavaClass = Repository.lookupClass(refName); return Analyze.deepInstanceOf(refJavaClass, remote); } catch (ClassNotFoundException e) { return 0.99; } } private static boolean isPrimitiveComponentClass(String refSig) { int c = 0; while (c < refSig.length() && refSig.charAt(c) == '[') { c++; } // If the string is now empty, then we evidently have // an invalid type signature. We'll return "true", // which in turn will cause isDeepSerializable() to return // 1.0, hopefully avoiding any warnings from being generated // by whatever detector is calling us. return c >= refSig.length() || refSig.charAt(c) != 'L'; } public static String getComponentClass(ReferenceType refType) { return getComponentClass(refType.getSignature()); } public static String getComponentClass(String refSig) { while (refSig.charAt(0) == '[') refSig = refSig.substring(1); // TODO: This method now returns primitive type signatures, is this ok? if (refSig.charAt(0) == 'L') return refSig.substring(1, refSig.length() - 1).replace('/', '.'); return refSig; } public static double isDeepSerializable(JavaClass x) throws ClassNotFoundException { if (storedException != null) throw storedException; if (x.getClassName().equals("java.lang.Object")) return 0.4; if (DEBUG) { System.out.println("checking " + x.getClassName()); } double result = Analyze.deepInstanceOf(x, serializable); if (result >= 0.9) { if (DEBUG) { System.out.println("Direct high serializable result: " + result); } return result; } if (x.isFinal()) return result; double collectionResult = Analyze.deepInstanceOf(x, collection); double mapResult = Analyze.deepInstanceOf(x, map); if (x.isInterface() || x.isAbstract()) { result = Math.max(result, Math.max(mapResult, collectionResult) * 0.95); if (result >= 0.9) { return result; } } ClassDescriptor classDescriptor = DescriptorFactory.createClassDescriptor(x); Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2(); Set<ClassDescriptor> directSubtypes = subtypes2.getDirectSubtypes(classDescriptor); directSubtypes.remove(classDescriptor); double confidence = 0.6; if (x.isAbstract() || x.isInterface()) { confidence = 0.8; result = Math.max(result, 0.4); } else if (directSubtypes.isEmpty()) confidence = 0.2; double confidence2 = (1 + confidence) / 2; result = Math.max(result, confidence2 * collectionResult); if (result >= 0.9) { if (DEBUG) { System.out.println("High collection result: " + result); } return result; } result = Math.max(result, confidence2 * mapResult); if (result >= 0.9) { if (DEBUG) { System.out.println("High map result: " + result); } return result; } result = Math.max(result, confidence2 * 0.5 * Analyze.deepInstanceOf(x, comparator)); if (result >= 0.9) { if (DEBUG) { System.out.println("High comparator result: " + result); } return result; } for (ClassDescriptor subtype : directSubtypes) { JavaClass subJavaClass = Repository.lookupClass(subtype.getDottedClassName()); result = Math.max(result, confidence * Analyze.deepInstanceOf(subJavaClass, serializable)); // result = Math.max(result, confidence * isDeepSerializable(subJavaClass)); if (result >= 0.9) { return result; } } if (DEBUG) { System.out.println("No high results; max: " + result); } return result; } /** * Given two JavaClasses, try to estimate the probability that an reference of type x is also an * instance of type y. Will return 0 only if it is impossible and 1 only if it is guaranteed. * * @param x Known type of object * @param y Type queried about * @return 0 - 1 value indicating probability */ public static double deepInstanceOf(@DottedClassName String x, @DottedClassName String y) throws ClassNotFoundException { return Analyze.deepInstanceOf(x, y); } /** * Given two JavaClasses, try to estimate the probability that an reference of type x is also an * instance of type y. Will return 0 only if it is impossible and 1 only if it is guaranteed. * * @param x Known type of object * @param y Type queried about * @return 0 - 1 value indicating probability */ public static double deepInstanceOf(JavaClass x, JavaClass y) throws ClassNotFoundException { return Analyze.deepInstanceOf(x, y); } }
/** * Based on the contents of the application directories/archives in a Project, and a "root" source * directory (under which some number of "real" source directories may be located), scan to find the * source directories containing the application's source files. * * @author David Hovemeyer */ public class DiscoverSourceDirectories { private static boolean DEBUG = SystemProperties.getBoolean("findbugs.dsd.debug"); /** Progress callback interface for reporting the progress of source directory discovery. */ public interface Progress extends IClassPathBuilderProgress { public void startRecursiveDirectorySearch(); public void doneRecursiveDirectorySearch(); public void startScanningArchives(int numArchivesToScan); public void doneScanningArchives(); public void startScanningClasses(int numClassesToScan); public void finishClass(); public void doneScanningClasses(); } private static class NoOpErrorLogger implements IErrorLogger { @Override public void reportMissingClass(ClassNotFoundException ex) {} @Override public void reportMissingClass(ClassDescriptor classDescriptor) {} @Override public void logError(String message) {} @Override public void logError(String message, Throwable e) {} @Override public void reportSkippedAnalysis(MethodDescriptor method) {} } private static class NoOpProgress implements Progress { @Override public void startScanningArchives(int numArchivesToScan) {} @Override public void doneScanningArchives() {} @Override public void startScanningClasses(int numClassesToScan) {} @Override public void finishClass() {} @Override public void doneScanningClasses() {} @Override public void finishArchive() {} @Override public void startRecursiveDirectorySearch() {} @Override public void doneRecursiveDirectorySearch() {} @Override public void startArchive(String name) {} } private Project project; private String rootSourceDirectory; private boolean scanForNestedArchives; private IErrorLogger errorLogger; private Progress progress; private final List<String> discoveredSourceDirectoryList; /** Constructor. */ public DiscoverSourceDirectories() { this.errorLogger = new NoOpErrorLogger(); this.progress = new NoOpProgress(); this.discoveredSourceDirectoryList = new LinkedList<String>(); } /** * Set the Project for which we want to find source directories. * * @param project Project for which we want to find source directories */ public void setProject(Project project) { this.project = project; } /** * Set the "root" source directory: we expect all of the actual source directories to be * underneath it. * * @param rootSourceDirectory the root source directory */ public void setRootSourceDirectory(String rootSourceDirectory) { this.rootSourceDirectory = rootSourceDirectory; } /** * Set whether or not to scan the project for nested archives (i.e., if there is a WAR or EAR file * that contains jar files inside it.) Default is false. * * @param scanForNestedArchives true if nested archives should be scanned, false otherwise */ public void setScanForNestedArchives(boolean scanForNestedArchives) { this.scanForNestedArchives = scanForNestedArchives; } /** * Set the error logger to use to report errors during scanning. By default, a no-op error logger * is used. * * @param errorLogger error logger to use to report errors during scanning */ public void setErrorLogger(IErrorLogger errorLogger) { this.errorLogger = errorLogger; } /** * Set the progress callback to which scanning progress should be reported. * * @param progress the progress callback */ public void setProgress(Progress progress) { this.progress = progress; } /** * Get the list of discovered source directories. These can be added to a Project. * * @return list of discovered source directories. */ public List<String> getDiscoveredSourceDirectoryList() { return Collections.unmodifiableList(discoveredSourceDirectoryList); } /** * Execute the search for source directories. * * @throws edu.umd.cs.findbugs.classfile.CheckedAnalysisException * @throws java.io.IOException * @throws java.lang.InterruptedException */ public void execute() throws CheckedAnalysisException, IOException, InterruptedException { File dir = new File(rootSourceDirectory); if (!dir.isDirectory()) { throw new IOException("Path " + rootSourceDirectory + " is not a directory"); } // Find all directories underneath the root source directory progress.startRecursiveDirectorySearch(); RecursiveFileSearch rfs = new RecursiveFileSearch( rootSourceDirectory, new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isDirectory(); } }); rfs.search(); progress.doneRecursiveDirectorySearch(); List<String> candidateSourceDirList = rfs.getDirectoriesScanned(); // Build the classpath IClassPath classPath = null; try { IClassFactory factory = ClassFactory.instance(); IClassPathBuilder builder = factory.createClassPathBuilder(errorLogger); classPath = buildClassPath(builder, factory); // From the application classes, find the full list of // fully-qualified source file names. List<String> fullyQualifiedSourceFileNameList = findFullyQualifiedSourceFileNames(builder, classPath); // Attempt to find source directories for all source files, // and add them to the discoveredSourceDirectoryList if (DEBUG) { System.out.println("looking for " + fullyQualifiedSourceFileNameList.size() + " files"); } findSourceDirectoriesForAllSourceFiles( fullyQualifiedSourceFileNameList, candidateSourceDirList); } finally { if (classPath != null) { classPath.close(); } } } private IClassPath buildClassPath(IClassPathBuilder builder, IClassFactory factory) throws InterruptedException, IOException, CheckedAnalysisException { progress.startScanningArchives(project.getFileCount()); for (String path : project.getFileList()) { builder.addCodeBase(factory.createFilesystemCodeBaseLocator(path), true); } for (String path : project.getAuxClasspathEntryList()) { builder.addCodeBase(factory.createFilesystemCodeBaseLocator(path), false); } IClassPath classPath = factory.createClassPath(); builder.build(classPath, progress); progress.doneScanningArchives(); return classPath; } private String findFullyQualifiedSourceFileName(IClassPath classPath, ClassDescriptor classDesc) throws IOException, CheckedAnalysisException { try { // Open and parse the class file to attempt // to discover the source file name. ICodeBaseEntry codeBaseEntry = classPath.lookupResource(classDesc.toResourceName()); ClassParserUsingASM classParser = new ClassParserUsingASM( new ClassReader(codeBaseEntry.openResource()), classDesc, codeBaseEntry); ClassInfo.Builder classInfoBuilder = new ClassInfo.Builder(); classParser.parse(classInfoBuilder); ClassInfo classInfo = classInfoBuilder.build(); // Construct the fully-qualified source file name // based on the package name and source file name. String packageName = classDesc.getPackageName(); String sourceFile = classInfo.getSource(); if (!packageName.equals("")) { packageName = packageName.replace('.', '/'); packageName += "/"; } String fullyQualifiedSourceFile = packageName + sourceFile; return fullyQualifiedSourceFile; } catch (CheckedAnalysisException e) { errorLogger.logError("Could scan class " + classDesc.toDottedClassName(), e); throw e; } finally { progress.finishClass(); } } private List<String> findFullyQualifiedSourceFileNames( IClassPathBuilder builder, IClassPath classPath) { List<ClassDescriptor> appClassList = builder.getAppClassList(); progress.startScanningClasses(appClassList.size()); List<String> fullyQualifiedSourceFileNameList = new LinkedList<String>(); for (ClassDescriptor classDesc : appClassList) { try { String fullyQualifiedSourceFileName = findFullyQualifiedSourceFileName(classPath, classDesc); fullyQualifiedSourceFileNameList.add(fullyQualifiedSourceFileName); } catch (IOException e) { errorLogger.logError("Couldn't scan class " + classDesc.toDottedClassName(), e); } catch (CheckedAnalysisException e) { errorLogger.logError("Couldn't scan class " + classDesc.toDottedClassName(), e); } } progress.doneScanningClasses(); return fullyQualifiedSourceFileNameList; } private void findSourceDirectoriesForAllSourceFiles( List<String> fullyQualifiedSourceFileNameList, List<String> candidateSourceDirList) { Set<String> sourceDirsFound = new HashSet<String>(); // For each source file discovered, try to locate it in one of // the candidate source directories. for (String fullyQualifiedSourceFileName : fullyQualifiedSourceFileNameList) { checkCandidateSourceDirs: for (String candidateSourceDir : candidateSourceDirList) { String path = candidateSourceDir + File.separatorChar + fullyQualifiedSourceFileName; File f = new File(path); if (DEBUG) { System.out.print("Checking " + f.getPath() + "..."); } boolean found = f.exists() && !f.isDirectory(); if (DEBUG) { System.out.println(found ? "FOUND" : "not found"); } if (found) { // Bingo! if (sourceDirsFound.add(candidateSourceDir)) { discoveredSourceDirectoryList.add(candidateSourceDir); sourceDirsFound.add(candidateSourceDir); } break checkCandidateSourceDirs; } } } } /** Just for testing. */ public static void main(String[] args) throws IOException, CheckedAnalysisException, InterruptedException { if (args.length != 2) { System.err.println( "Usage: " + DiscoverSourceDirectories.class.getName() + " <project file> <root source dir>"); System.exit(1); } Project project = Project.readProject(args[0]); IErrorLogger errorLogger = new IErrorLogger() { @Override public void reportMissingClass(ClassNotFoundException ex) { String className = ClassNotFoundExceptionParser.getMissingClassName(ex); if (className != null) { logError("Missing class: " + className); } else { logError("Missing class: " + ex); } } @Override public void reportMissingClass(ClassDescriptor classDescriptor) { logError("Missing class: " + classDescriptor.toDottedClassName()); } @Override public void logError(String message) { System.err.println("Error: " + message); } @Override public void logError(String message, Throwable e) { logError(message + ": " + e.getMessage()); } @Override public void reportSkippedAnalysis(MethodDescriptor method) { logError("Skipped analysis of method " + method.toString()); } }; DiscoverSourceDirectories.Progress progress = new DiscoverSourceDirectories.Progress() { @Override public void startRecursiveDirectorySearch() { System.out.print("Scanning directories..."); System.out.flush(); } @Override public void doneRecursiveDirectorySearch() { System.out.println("done"); } @Override public void startScanningArchives(int numArchivesToScan) { System.out.print("Scanning " + numArchivesToScan + " archives.."); System.out.flush(); } @Override public void doneScanningArchives() { System.out.println("done"); } @Override public void startScanningClasses(int numClassesToScan) { System.out.print("Scanning " + numClassesToScan + " classes..."); System.out.flush(); } @Override public void finishClass() { System.out.print("."); System.out.flush(); } @Override public void doneScanningClasses() { System.out.println("done"); } @Override public void finishArchive() { System.out.print("."); System.out.flush(); } @Override public void startArchive(String name) { // noop } }; DiscoverSourceDirectories discoverSourceDirectories = new DiscoverSourceDirectories(); discoverSourceDirectories.setProject(project); discoverSourceDirectories.setRootSourceDirectory(args[1]); discoverSourceDirectories.setErrorLogger(errorLogger); discoverSourceDirectories.setProgress(progress); discoverSourceDirectories.execute(); System.out.println("Found source directories:"); for (String srcDir : discoverSourceDirectories.getDiscoveredSourceDirectoryList()) { System.out.println(" " + srcDir); } } }
/** * An abstract class which provides much of the functionality required of all BugReporter objects. */ public abstract class AbstractBugReporter implements BugReporter { private static final boolean DEBUG = SystemProperties.getBoolean("abreporter.debug"); private static final boolean DEBUG_MISSING_CLASSES = SystemProperties.getBoolean("findbugs.debug.missingclasses"); protected static class Error { private int sequence; private String message; private Throwable cause; public Error(int sequence, String message) { this(sequence, message, null); } public Error(int sequence, String message, Throwable cause) { this.sequence = sequence; this.message = message; this.cause = cause; } public int getSequence() { return sequence; } public String getMessage() { return message; } public Throwable getCause() { return cause; } @Override public int hashCode() { int hashCode = message.hashCode(); if (cause != null) { hashCode += 1009 * cause.hashCode(); } return hashCode; } @Override public boolean equals(Object obj) { if (obj == null || obj.getClass() != this.getClass()) { return false; } Error other = (Error) obj; if (!message.equals(other.message)) { return false; } if (this.cause == other.cause) { return true; } if (this.cause == null || other.cause == null) { return false; } return this.cause.equals(other.cause); } } private int verbosityLevel; private int priorityThreshold; private int rankThreshold; private boolean analysisUnderway, relaxed; private int errorCount; private final Set<String> missingClassMessageList; private final Set<Error> errorSet; private final List<BugReporterObserver> observerList; private final ProjectStats projectStats; public AbstractBugReporter() { super(); verbosityLevel = NORMAL; missingClassMessageList = new LinkedHashSet<String>(); errorSet = new HashSet<Error>(); observerList = new LinkedList<BugReporterObserver>(); projectStats = new ProjectStats(); // bug 2815983: no bugs are reported anymore // there is no info which value should be default, so using the "any one" rankThreshold = 42; } public void setErrorVerbosity(int level) { this.verbosityLevel = level; } public void setPriorityThreshold(int threshold) { this.priorityThreshold = threshold; } public void setRankThreshold(int threshold) { this.rankThreshold = threshold; } // Subclasses must override doReportBug(), not this method. public final void reportBug(BugInstance bugInstance) { if (priorityThreshold == 0) { throw new IllegalStateException("Priority threshold not set"); } if (!analysisUnderway) { if (FindBugsAnalysisFeatures.isRelaxedMode()) { relaxed = true; } analysisUnderway = true; } ClassAnnotation primaryClass = bugInstance.getPrimaryClass(); if (primaryClass != null && !AnalysisContext.currentAnalysisContext() .isApplicationClass(primaryClass.getClassName())) { if (DEBUG) { System.out.println("AbstractBugReporter: Filtering due to non-primary class"); } return; } int priority = bugInstance.getPriority(); int bugRank = bugInstance.getBugRank(); if (priority <= priorityThreshold && bugRank <= rankThreshold || relaxed) { doReportBug(bugInstance); } else { if (DEBUG) { if (priority <= priorityThreshold) System.out.println( "AbstractBugReporter: Filtering due to priorityThreshold " + priority + " > " + priorityThreshold); else System.out.println( "AbstractBugReporter: Filtering due to rankThreshold " + bugRank + " > " + rankThreshold); } } } public final void reportBugsFromXml(@WillClose InputStream in, Project theProject) throws IOException, DocumentException { SortedBugCollection theCollection = new SortedBugCollection(theProject); theCollection.readXML(in); for (BugInstance bug : theCollection.getCollection()) { doReportBug(bug); } } public static String getMissingClassName(ClassNotFoundException ex) { String message = ex.getMessage(); if (message == null) { message = ""; } // Try to decode the error message by extracting the class name. String className = ClassNotFoundExceptionParser.getMissingClassName(ex); if (className != null) { if (className.indexOf('/') >= 0) { className = className.replace('/', '.'); } return className; } // Just return the entire message. // It hopefully will still make sense to the user. return message; } public void reportMissingClass(ClassNotFoundException ex) { if (DEBUG_MISSING_CLASSES) { System.out.println("Missing class: " + ex.toString()); ex.printStackTrace(System.out); } if (verbosityLevel == SILENT) { return; } logMissingClass(getMissingClassName(ex)); } protected static final boolean isValidMissingClassMessage(String message) { if (message == null) { return false; } message = message.trim(); if (message.startsWith("[")) { // Sometimes we see methods called on array classes. // Obviously, these don't exist as class files. // So, we should just ignore the exception. // Really, we should fix the class/method search interfaces // to be much more intelligent in resolving method // implementations. return false; } if (message.equals("")) { // Subtypes2 throws ClassNotFoundExceptions with no message in // some cases. Ignore them (the missing classes will already // have been reported). return false; } if (message.endsWith(".package-info")) { // we ignore all "package-info" issues return false; } if (message.equals("java.lang.Synthetic")) return false; return true; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IErrorLogger#reportMissingClass(edu.umd.cs.findbugs.classfile.ClassDescriptor) */ public void reportMissingClass(ClassDescriptor classDescriptor) { if (DEBUG_MISSING_CLASSES) { System.out.println("Missing class: " + classDescriptor); new Throwable().printStackTrace(System.out); } if (verbosityLevel == SILENT) { return; } logMissingClass(classDescriptor.toDottedClassName()); } /** @param message */ private void logMissingClass(String message) { if (!isValidMissingClassMessage(message)) { return; } missingClassMessageList.add(message); } /** * Report that we skipped some analysis of a method * * @param method */ public void reportSkippedAnalysis(MethodDescriptor method) { // TODO: log this } public void logError(String message) { if (verbosityLevel == SILENT) { return; } Error error = new Error(errorCount++, message); if (!errorSet.contains(error)) { errorSet.add(error); } } /** @return the set with all analysis errors reported so far */ protected Set<Error> getQueuedErrors() { return errorSet; } /** @return the set with all missing classes reported so far */ protected Set<String> getMissingClasses() { return missingClassMessageList; } public void logError(String message, Throwable e) { if (e instanceof MethodUnprofitableException) { // TODO: log this return; } if (e instanceof edu.umd.cs.findbugs.classfile.MissingClassException) { edu.umd.cs.findbugs.classfile.MissingClassException e2 = (edu.umd.cs.findbugs.classfile.MissingClassException) e; reportMissingClass(e2.getClassDescriptor()); return; } if (e instanceof edu.umd.cs.findbugs.ba.MissingClassException) { // Record the missing class, in case the exception thrower didn't. edu.umd.cs.findbugs.ba.MissingClassException missingClassEx = (edu.umd.cs.findbugs.ba.MissingClassException) e; ClassNotFoundException cnfe = missingClassEx.getClassNotFoundException(); reportMissingClass(cnfe); // Don't report dataflow analysis exceptions due to missing classes. // Too much noise. return; } if (verbosityLevel == SILENT) { return; } Error error = new Error(errorCount++, message, e); if (!errorSet.contains(error)) { errorSet.add(error); } } public void reportQueuedErrors() { // Report unique errors in order of their sequence Error[] errorList = errorSet.toArray(new Error[errorSet.size()]); Arrays.sort( errorList, new Comparator<Error>() { public int compare(Error o1, Error o2) { return o1.getSequence() - o2.getSequence(); } }); for (Error error : errorList) { reportAnalysisError(new AnalysisError(error.getMessage(), error.getCause())); } for (String aMissingClassMessageList : missingClassMessageList) { reportMissingClass(aMissingClassMessageList); } } public void addObserver(BugReporterObserver observer) { observerList.add(observer); } public ProjectStats getProjectStats() { return projectStats; } /** * This should be called when a bug is reported by a subclass. * * @param bugInstance the bug to inform observers of */ protected void notifyObservers(BugInstance bugInstance) { for (BugReporterObserver aObserverList : observerList) { aObserverList.reportBug(bugInstance); } } /** * Subclasses must override this. It will be called only for bugs which meet the priority * threshold. * * @param bugInstance the bug to report */ protected abstract void doReportBug(BugInstance bugInstance); /** * Report a queued error. * * @param error the queued error */ public abstract void reportAnalysisError(AnalysisError error); /** * Report a missing class. * * @param string the name of the class */ public abstract void reportMissingClass(String string); }