/**
 * 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;
  }
}
Exemplo n.º 2
0
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);
}