Beispiel #1
0
/**
 * A context for analysis of a complete project. This serves as the repository for whole-program
 * information and data structures.
 *
 * <p><b>NOTE</b>: this class is slated to become obsolete. New code should use the IAnalysisCache
 * object returned by Global.getAnalysisCache() to access all analysis information (global
 * databases, class and method analyses, etc.)
 *
 * @author David Hovemeyer
 * @see edu.umd.cs.findbugs.classfile.IAnalysisCache
 * @see edu.umd.cs.findbugs.classfile.Global
 */
@NotThreadSafe
public class AnalysisContext {
  public static final boolean DEBUG = SystemProperties.getBoolean("findbugs.analysiscontext.debug");

  public static final boolean IGNORE_BUILTIN_MODELS =
      SystemProperties.getBoolean("findbugs.ignoreBuiltinModels");

  public static final String DEFAULT_NONNULL_PARAM_DATABASE_FILENAME = "nonnullParam.db";

  public static final String DEFAULT_CHECK_FOR_NULL_PARAM_DATABASE_FILENAME =
      "checkForNullParam.db";

  public static final String DEFAULT_NULL_RETURN_VALUE_ANNOTATION_DATABASE = "nullReturn.db";

  public static final String UNCONDITIONAL_DEREF_DB_FILENAME = "unconditionalDeref.db";

  public static final String NONNULL_RETURN_DB_FILENAME = "nonnullReturn.db";

  public static final String UNCONDITIONAL_DEREF_DB_RESOURCE = "jdkBaseUnconditionalDeref.db";

  public static final String NONNULL_RETURN_DB_RESOURCE = "jdkBaseNonnullReturn.db";

  public static final String DEFAULT_NULL_RETURN_VALUE_DB_FILENAME = "mayReturnNull.db";

  private static InheritableThreadLocal<AnalysisContext> currentAnalysisContext =
      new InheritableThreadLocal<AnalysisContext>() {
        @Override
        public AnalysisContext initialValue() {
          // throw new
          // IllegalStateException("currentAnalysisContext should be set by
          // AnalysisContext.setCurrentAnalysisContext");
          return null;
        }
      };

  private static AnalysisLocal<XFactory> currentXFactory =
      new AnalysisLocal<XFactory>() {
        @Override
        public XFactory initialValue() {
          throw new IllegalStateException(
              "currentXFactory should be set by AnalysisContext.setCurrentAnalysisContext");
        }
      };

  /**
   * save the original SyntheticRepository so we may obtain JavaClass objects which we can reuse. (A
   * URLClassPathRepository gets closed after analysis.)
   */
  private static final org.apache.bcel.util.Repository originalRepository =
      Repository.getRepository(); // BCEL
  // SyntheticRepository

  /**
   * Default maximum number of ClassContext objects to cache. FIXME: need to evaluate this
   * parameter. Need to keep stats about accesses.
   */
  //    private static final int DEFAULT_CACHE_SIZE = 3;

  // Instance fields
  private BitSet boolPropertySet;

  private String databaseInputDir;

  private String databaseOutputDir;

  boolean missingClassWarningsSuppressed;

  private ClassSummary classSummary;

  private ClassDescriptor classBeingAnalyzed;

  private FieldSummary fieldSummary;

  private UnreadFields unreadFields;

  private TypeQualifierNullnessAnnotationDatabase tqNullnessDatabase;

  private final HashSet<MethodDescriptor> skippedDueToInvokeDynamic;

  private final Project project;

  private final EqualsKindSummary equalsKindSummary;

  private final UnreadFieldsData unreadFieldsData;

  private final SuppressionMatcher suppressionMatcher;

  protected final RepositoryLookupFailureCallback lookupFailureCallback;

  private final Map<MethodInfo, MethodInfo> bridgeTo;

  private final Map<MethodInfo, MethodInfo> bridgeFrom;

  public AnalysisContext(@Nonnull Project project) {
    requireNonNull(project);
    this.project = project;
    this.boolPropertySet = new BitSet();
    this.lookupFailureCallback = new DelegatingRepositoryLookupFailureCallback();
    skippedDueToInvokeDynamic = new HashSet<>();
    equalsKindSummary = new EqualsKindSummary();
    unreadFieldsData = new UnreadFieldsData();
    suppressionMatcher = new SuppressionMatcher();
    bridgeTo = new IdentityHashMap<>();
    bridgeFrom = new IdentityHashMap<>();
  }

  private void clear() {
    boolPropertySet = null;
    databaseInputDir = null;
    databaseOutputDir = null;
  }

  /** Get the AnalysisContext associated with this thread */
  public static AnalysisContext currentAnalysisContext() {
    return currentAnalysisContext.get();
  }

  public static XFactory currentXFactory() {
    return currentXFactory.get();
  }

  public ClassDescriptor getClassBeingAnalyzed() {
    return classBeingAnalyzed;
  }

  public void setClassBeingAnalyzed(@Nonnull ClassDescriptor classBeingAnalyzed) {
    this.classBeingAnalyzed = classBeingAnalyzed;
  }

  public void clearClassBeingAnalyzed() {
    this.classBeingAnalyzed = null;
  }

  public ClassSummary getClassSummary() {
    if (classSummary == null) {
      throw new IllegalStateException("ClassSummary not set");
    }
    return classSummary;
  }

  public void setClassSummary(@Nonnull ClassSummary classSummary) {
    if (this.classSummary != null) {
      throw new IllegalStateException("ClassSummary already set");
    }
    this.classSummary = classSummary;
  }

  public EqualsKindSummary getEqualsKindSummary() {
    return equalsKindSummary;
  }

  public FieldSummary getFieldSummary() {
    if (fieldSummary == null) {
      AnalysisContext.logError("Field Summary not set", new IllegalStateException());
      fieldSummary = new FieldSummary();
    }
    return fieldSummary;
  }

  public void setFieldSummary(@Nonnull FieldSummary fieldSummary) {
    if (this.fieldSummary != null) {
      AnalysisContext.logError("Field Summary already set", new IllegalStateException());
    }
    this.fieldSummary = fieldSummary;
  }

  public @Nonnull UnreadFieldsData getUnreadFieldsData() {
    return unreadFieldsData;
  }

  public @Nonnull UnreadFields getUnreadFields() {
    if (!unreadFieldsAvailable()) {
      throw new IllegalStateException();
    }
    return unreadFields;
  }

  public boolean unreadFieldsAvailable() {
    return unreadFields != null;
  }

  public void setUnreadFields(@Nonnull UnreadFields unreadFields) {
    if (this.unreadFields != null) {
      throw new IllegalStateException("UnreadFields detector already set");
    }
    this.unreadFields = unreadFields;
  }

  private static boolean skipReportingMissingClass(@CheckForNull @DottedClassName String missing) {
    return missing == null
        || missing.length() == 0
        || missing.charAt(0) == '['
        || missing.endsWith("package-info");
  }

  private static @CheckForNull RepositoryLookupFailureCallback getCurrentLookupFailureCallback() {
    AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
    if (currentAnalysisContext2 == null) {
      return null;
    }
    if (currentAnalysisContext2.missingClassWarningsSuppressed) {
      return null;
    }
    return currentAnalysisContext2.getLookupFailureCallback();
  }

  /**
   * file a ClassNotFoundException with the lookupFailureCallback
   *
   * @see #getLookupFailureCallback()
   */
  public static void reportMissingClass(ClassNotFoundException e) {
    requireNonNull(e, "argument is null");
    String missing = AbstractBugReporter.getMissingClassName(e);
    if (skipReportingMissingClass(missing)) {
      return;
    }
    if (!analyzingApplicationClass()) {
      return;
    }

    RepositoryLookupFailureCallback lookupFailureCallback = getCurrentLookupFailureCallback();
    if (lookupFailureCallback != null) {
      lookupFailureCallback.reportMissingClass(e);
    }
  }

  public static void reportMissingClass(edu.umd.cs.findbugs.ba.MissingClassException e) {
    requireNonNull(e, "argument is null");
    reportMissingClass(e.getClassDescriptor());
  }

  public static boolean analyzingApplicationClass() {
    AnalysisContext context = AnalysisContext.currentAnalysisContext();
    if (context == null) {
      return false;
    }
    ClassDescriptor clazz = context.getClassBeingAnalyzed();
    if (clazz == null) {
      return false;
    }
    return context.isApplicationClass(clazz);
  }

  public static void reportMissingClass(edu.umd.cs.findbugs.classfile.MissingClassException e) {
    requireNonNull(e, "argument is null");
    reportMissingClass(e.getClassDescriptor());
  }

  public static void reportMissingClass(ClassDescriptor c) {
    requireNonNull(c, "argument is null");
    if (!analyzingApplicationClass()) {
      return;
    }
    String missing = c.getDottedClassName();
    if (missing.length() == 1) {
      System.out.println(c);
    }
    if (skipReportingMissingClass(missing)) {
      return;
    }
    RepositoryLookupFailureCallback lookupFailureCallback = getCurrentLookupFailureCallback();
    if (lookupFailureCallback != null) {
      lookupFailureCallback.reportMissingClass(c);
    }
  }

  /** Report an error */
  public static void logError(String msg, Exception e) {
    AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
    if (currentAnalysisContext2 == null) {
      if (e instanceof NoSuchBugPattern) {
        return;
      }
      /*
      if (false && SystemProperties.ASSERTIONS_ENABLED) {
          AssertionError e2 = new AssertionError("Exception logged with no analysis context");
          e2.initCause(e);
          throw e2;
      }
       */
      e.printStackTrace(System.err);
      return;
    }
    if (e instanceof MissingClassException) {
      reportMissingClass(((MissingClassException) e).getClassNotFoundException());
      return;
    }
    if (e instanceof edu.umd.cs.findbugs.classfile.MissingClassException) {
      reportMissingClass(
          ((edu.umd.cs.findbugs.classfile.MissingClassException) e).toClassNotFoundException());
      return;
    }

    RepositoryLookupFailureCallback lookupFailureCallback =
        currentAnalysisContext2.getLookupFailureCallback();
    if (lookupFailureCallback != null) {
      lookupFailureCallback.logError(msg, e);
    }
  }

  /** Report an error */
  public static void logError(String msg) {
    AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
    if (currentAnalysisContext2 == null) {
      return;
    }
    currentAnalysisContext2.logAnError(msg);
  }

  public void logAnError(String msg) {
    RepositoryLookupFailureCallback lookupFailureCallback = getLookupFailureCallback();
    if (lookupFailureCallback != null) {
      lookupFailureCallback.logError(msg);
    }
  }

  public void analysisSkippedDueToInvokeDynamic(XMethod m) {
    if (!m.usesInvokeDynamic()) {
      throw new IllegalArgumentException();
    }
    if (skippedDueToInvokeDynamic.add(m.getMethodDescriptor())) {
      logAnError(m + " skipped due to invoke_dynamic");
    }
  }

  public boolean setMissingClassWarningsSuppressed(boolean value) {
    boolean oldValue = missingClassWarningsSuppressed;
    missingClassWarningsSuppressed = value;
    return oldValue;
  }

  // /**
  // * Add an application class to the repository.
  // *
  // * @param appClass the application class
  // */
  // public abstract void addApplicationClassToRepository(JavaClass appClass);

  /**
   * Return whether or not the given class is an application class.
   *
   * @param cls the class to lookup
   * @return true if the class is an application class, false if not an application class or if the
   *     class cannot be located
   */
  public boolean isApplicationClass(JavaClass cls) {
    // return getSubtypes().isApplicationClass(cls);
    return getSubtypes2().isApplicationClass(DescriptorFactory.createClassDescriptor(cls));
  }

  /**
   * Return whether or not the given class is an application class.
   *
   * @param className name of a class
   * @return true if the class is an application class, false if not an application class or if the
   *     class cannot be located
   */
  public boolean isApplicationClass(@DottedClassName String className) {
    // try {
    // JavaClass javaClass = lookupClass(className);
    // return isApplicationClass(javaClass);
    // } catch (ClassNotFoundException e) {
    // AnalysisContext.reportMissingClass(e);
    // return false;
    // }
    ClassDescriptor classDesc =
        DescriptorFactory.createClassDescriptorFromDottedClassName(className);
    return getSubtypes2().isApplicationClass(classDesc);
  }

  public boolean isApplicationClass(ClassDescriptor desc) {
    return getSubtypes2().isApplicationClass(desc);
  }

  public int getClassSize(ClassDescriptor desc) {
    IAnalysisCache analysisCache = Global.getAnalysisCache();

    try {
      /* ClassContext classContext =*/ analysisCache.getClassAnalysis(ClassContext.class, desc);
      ClassData classData = analysisCache.getClassAnalysis(ClassData.class, desc);
      return classData.getData().length;

    } catch (RuntimeException e) {
      AnalysisContext.logError("Error getting class data for " + desc, e);
      return 10000;
    } catch (CheckedAnalysisException e) {
      AnalysisContext.logError("Could not get class context for " + desc, e);
      return 10000;
    }
  }

  public boolean isTooBig(ClassDescriptor desc) {
    IAnalysisCache analysisCache = Global.getAnalysisCache();

    try {
      ClassContext classContext = analysisCache.getClassAnalysis(ClassContext.class, desc);
      ClassData classData = analysisCache.getClassAnalysis(ClassData.class, desc);
      if (classData.getData().length > 1000000) {
        return true;
      }
      try {
        JavaClass javaClass = classContext.getJavaClass();
        if (javaClass.getMethods().length > 1000) {
          return true;
        }
      } catch (RuntimeException e) {
        AnalysisContext.logError(
            "Error parsing class " + desc + " from " + classData.getCodeBaseEntry().getCodeBase(),
            e);
        return true;
      }

    } catch (RuntimeException e) {
      AnalysisContext.logError("Error getting class data for " + desc, e);
      return true;
    } catch (CheckedAnalysisException e) {
      AnalysisContext.logError("Could not get class context for " + desc, e);
      return true;
    }
    return false;
  }

  /**
   * Lookup a class. <em>Use this method instead of Repository.lookupClass().</em>
   *
   * @param classDescriptor descriptor specifying the class to look up
   * @return the class
   * @throws ClassNotFoundException if the class can't be found
   */
  public JavaClass lookupClass(@Nonnull ClassDescriptor classDescriptor)
      throws ClassNotFoundException {
    return lookupClass(classDescriptor.toDottedClassName());
  }

  /**
   * This is equivalent to Repository.lookupClass() or this.lookupClass(), except it uses the
   * original Repository instead of the current one.
   *
   * <p>This can be important because URLClassPathRepository objects are closed after an analysis,
   * so JavaClass objects obtained from them are no good on subsequent runs.
   *
   * @param className the name of the class
   * @return the JavaClass representing the class
   * @throws ClassNotFoundException
   */
  public static JavaClass lookupSystemClass(@Nonnull String className)
      throws ClassNotFoundException {
    // TODO: eventually we should move to our own thread-safe repository
    // implementation
    requireNonNull(className, "className is null");
    if (originalRepository == null) {
      throw new IllegalStateException("originalRepository is null");
    }

    JavaClass clazz = originalRepository.findClass(className);
    return (clazz == null ? originalRepository.loadClass(className) : clazz);
  }

  /**
   * Lookup a class's source file
   *
   * @param dottedClassName the name of the class
   * @return the source file for the class, or {@link SourceLineAnnotation#UNKNOWN_SOURCE_FILE} if
   *     unable to determine
   */
  public final String lookupSourceFile(@Nonnull @DottedClassName String dottedClassName) {
    requireNonNull(dottedClassName, "className is null");
    try {
      XClass xClass =
          Global.getAnalysisCache()
              .getClassAnalysis(
                  XClass.class,
                  DescriptorFactory.createClassDescriptorFromDottedClassName(dottedClassName));
      String name = xClass.getSource();
      if (name == null) {
        return SourceLineAnnotation.UNKNOWN_SOURCE_FILE;
      }
      return name;
    } catch (CheckedAnalysisException e) {
      return SourceLineAnnotation.UNKNOWN_SOURCE_FILE;
    }
  }

  /** If possible, load interprocedural property databases. */
  public final void loadInterproceduralDatabases() {
    loadPropertyDatabase(
        getFieldStoreTypeDatabase(),
        FieldStoreTypeDatabase.DEFAULT_FILENAME,
        "field store type database");
    loadPropertyDatabase(
        getUnconditionalDerefParamDatabase(),
        UNCONDITIONAL_DEREF_DB_FILENAME,
        "unconditional param deref database");
    loadPropertyDatabase(
        getReturnValueNullnessPropertyDatabase(),
        NONNULL_RETURN_DB_FILENAME,
        "nonnull return db database");
  }

  /**
   * If possible, load default (built-in) interprocedural property databases. These are the
   * databases for things like Java core APIs that unconditional dereference parameters.
   */
  public final void loadDefaultInterproceduralDatabases() {
    if (IGNORE_BUILTIN_MODELS) {
      return;
    }
    loadPropertyDatabaseFromResource(
        getUnconditionalDerefParamDatabase(),
        UNCONDITIONAL_DEREF_DB_RESOURCE,
        "unconditional param deref database");
    loadPropertyDatabaseFromResource(
        getReturnValueNullnessPropertyDatabase(),
        NONNULL_RETURN_DB_RESOURCE,
        "nonnull return db database");
  }

  /**
   * Set a boolean property.
   *
   * @param prop the property to set
   * @param value the value of the property
   */
  public final void setBoolProperty(@AnalysisFeature int prop, boolean value) {
    boolPropertySet.set(prop, value);
  }

  /**
   * Get a boolean property.
   *
   * @param prop the property
   * @return value of the property; defaults to false if the property has not had a value assigned
   *     explicitly
   */
  public final boolean getBoolProperty(@AnalysisFeature int prop) {
    return boolPropertySet.get(prop);
  }

  /**
   * Set the interprocedural database input directory.
   *
   * @param databaseInputDir the interprocedural database input directory
   */
  public final void setDatabaseInputDir(String databaseInputDir) {
    if (DEBUG) {
      System.out.println("Setting database input directory: " + databaseInputDir);
    }
    this.databaseInputDir = databaseInputDir;
  }

  /**
   * Get the interprocedural database input directory.
   *
   * @return the interprocedural database input directory
   */
  public final String getDatabaseInputDir() {
    return databaseInputDir;
  }

  /**
   * Set the interprocedural database output directory.
   *
   * @param databaseOutputDir the interprocedural database output directory
   */
  public final void setDatabaseOutputDir(String databaseOutputDir) {
    if (DEBUG) {
      System.out.println("Setting database output directory: " + databaseOutputDir);
    }
    this.databaseOutputDir = databaseOutputDir;
  }

  /**
   * Get the interprocedural database output directory.
   *
   * @return the interprocedural database output directory
   */
  public final String getDatabaseOutputDir() {
    return databaseOutputDir;
  }

  /**
   * Load an interprocedural property database.
   *
   * @param <DatabaseType> actual type of the database
   * @param <KeyType> type of key (e.g., method or field)
   * @param <Property> type of properties stored in the database
   * @param database the empty database object
   * @param fileName file to load database from
   * @param description description of the database (for diagnostics)
   * @return the database object, or null if the database couldn't be loaded
   */
  public <
          DatabaseType extends PropertyDatabase<KeyType, Property>,
          KeyType extends FieldOrMethodDescriptor,
          Property>
      DatabaseType loadPropertyDatabase(
          DatabaseType database, String fileName, String description) {
    try {
      File dbFile = new File(getDatabaseInputDir(), fileName);
      if (DEBUG) {
        System.out.println("Loading " + description + " from " + dbFile.getPath() + "...");
      }

      database.readFromFile(dbFile.getPath());
      return database;
    } catch (IOException e) {
      getLookupFailureCallback().logError("Error loading " + description, e);
    } catch (PropertyDatabaseFormatException e) {
      getLookupFailureCallback().logError("Invalid " + description, e);
    }
    return null;
  }

  /**
   * Load an interprocedural property database.
   *
   * @param <DatabaseType> actual type of the database
   * @param <KeyType> type of key (e.g., method or field)
   * @param <Property> type of properties stored in the database
   * @param database the empty database object
   * @param resourceName name of resource to load the database from
   * @param description description of the database (for diagnostics)
   * @return the database object, or null if the database couldn't be loaded
   */
  public <
          DatabaseType extends PropertyDatabase<KeyType, Property>,
          KeyType extends FieldOrMethodDescriptor,
          Property>
      DatabaseType loadPropertyDatabaseFromResource(
          DatabaseType database, String resourceName, String description) {
    try {
      if (DEBUG) {
        System.out.println(
            "Loading default "
                + description
                + " from "
                + resourceName
                + " @ "
                + database.getClass().getResource(resourceName)
                + " ... ");
      }
      try (InputStream in = database.getClass().getResourceAsStream(resourceName)) {
        if (in == null) {
          AnalysisContext.logError(
              "Unable to load " + description + " from resource " + resourceName);
        } else {
          database.read(in);
        }
      }
      return database;
    } catch (IOException e) {
      getLookupFailureCallback().logError("Error loading " + description, e);
    } catch (PropertyDatabaseFormatException e) {
      getLookupFailureCallback().logError("Invalid " + description, e);
    }
    return null;
  }

  /**
   * Write an interprocedural property database.
   *
   * @param <DatabaseType> actual type of the database
   * @param <KeyType> type of key (e.g., method or field)
   * @param <Property> type of properties stored in the database
   * @param database the database
   * @param fileName name of database file
   * @param description description of the database
   */
  public <
          DatabaseType extends PropertyDatabase<KeyType, Property>,
          KeyType extends FieldOrMethodDescriptor,
          Property>
      void storePropertyDatabase(DatabaseType database, String fileName, String description) {

    try {
      File dbFile = new File(getDatabaseOutputDir(), fileName);
      if (DEBUG) {
        System.out.println("Writing " + description + " to " + dbFile.getPath() + "...");
      }
      database.writeToFile(dbFile.getPath());
    } catch (IOException e) {
      getLookupFailureCallback().logError("Error writing " + description, e);
    }
  }

  /**
   * Set the current analysis context for this thread.
   *
   * @param analysisContext the current analysis context for this thread
   */
  public static void setCurrentAnalysisContext(AnalysisContext analysisContext) {
    currentAnalysisContext.set(analysisContext);
    if (Global.getAnalysisCache() != null) {
      currentXFactory.set(new XFactory());
    }
  }

  public static void removeCurrentAnalysisContext() {
    AnalysisContext context = currentAnalysisContext();
    if (context != null) {
      context.clear();
    }
    currentAnalysisContext.remove();
  }

  /**
   * Get Collection of all XClass objects seen so far.
   *
   * @return Collection of all XClass objects seen so far
   */
  public Collection<XClass> getXClassCollection() {
    return getSubtypes2().getXClassCollection();
  }

  public SuppressionMatcher getSuppressionMatcher() {
    return suppressionMatcher;
  }

  /**
   * Add an entry to the Repository's classpath.
   *
   * @param url the classpath entry URL
   * @throws IOException
   */
  public void addClasspathEntry(String url) throws IOException {
    throw new UnsupportedOperationException();
  }

  /** Clear the ClassContext cache. This should be done between analysis passes. */
  public void clearClassContextCache() {
    throw new UnsupportedOperationException();
  }

  /** Clear the BCEL Repository in preparation for analysis. */
  public void clearRepository() {
    // Set the backing store for the BCEL Repository to
    // be the AnalysisCache.
    Repository.setRepository(new AnalysisCacheToRepositoryAdapter());
  }

  public AnnotationRetentionDatabase getAnnotationRetentionDatabase() {
    return getDatabase(AnnotationRetentionDatabase.class);
  }

  public CheckReturnAnnotationDatabase getCheckReturnAnnotationDatabase() {
    return getDatabase(CheckReturnAnnotationDatabase.class);
  }

  /**
   * Get the ClassContext for a class.
   *
   * @param javaClass the class
   * @return the ClassContext for that class
   */
  public ClassContext getClassContext(JavaClass javaClass) {
    // This is a bit silly since we're doing an unnecessary
    // ClassDescriptor->JavaClass lookup.
    // However, we can be assured that it will succeed.

    ClassDescriptor classDescriptor =
        DescriptorFactory.instance()
            .getClassDescriptor(ClassName.toSlashedClassName(javaClass.getClassName()));

    try {
      return Global.getAnalysisCache().getClassAnalysis(ClassContext.class, classDescriptor);
    } catch (CheckedAnalysisException e) {
      IllegalStateException ise =
          new IllegalStateException("Could not get ClassContext for JavaClass");
      ise.initCause(e);
      throw ise;
    }
  }

  /**
   * Get stats about hit rate for ClassContext cache.
   *
   * @return stats about hit rate for ClassContext cache
   */
  public String getClassContextStats() {
    return "<unknown ClassContext stats>";
  }

  /**
   * Get the property database recording the types of values stored into fields.
   *
   * @return the database, or null if there is no database available
   */
  public FieldStoreTypeDatabase getFieldStoreTypeDatabase() {
    return getDatabase(FieldStoreTypeDatabase.class);
  }

  public JCIPAnnotationDatabase getJCIPAnnotationDatabase() {
    return getDatabase(JCIPAnnotationDatabase.class);
  }

  /** Get the lookup failure callback. */
  public RepositoryLookupFailureCallback getLookupFailureCallback() {
    return lookupFailureCallback;
  }

  /** Get the SourceFinder, for finding source files. */
  public SourceFinder getSourceFinder() {
    return project.getSourceFinder();
  }

  /** Get the SourceInfoMap. */
  public SourceInfoMap getSourceInfoMap() {
    return getDatabase(SourceInfoMap.class);
  }

  /**
   * Get the property database recording which methods unconditionally dereference parameters.
   *
   * @return the database, or null if there is no database available
   */
  public ParameterNullnessPropertyDatabase getUnconditionalDerefParamDatabase() {
    return getDatabase(ParameterNullnessPropertyDatabase.class);
  }

  /**
   * Instantiate the CheckReturnAnnotationDatabase. Do this after the repository has been set up.
   */
  public void initDatabases() {
    // Databases are created on-demand - don't need to explicitly create
    // them
  }

  /**
   * Lookup a class. <em>Use this method instead of Repository.lookupClass().</em>
   *
   * @param className the name of the class
   * @return the JavaClass representing the class
   * @throws ClassNotFoundException (but not really)
   */
  public JavaClass lookupClass(@Nonnull @DottedClassName String className)
      throws ClassNotFoundException {
    try {
      if (className.length() == 0) {
        throw new IllegalArgumentException("Class name is empty");
      }
      if (!ClassName.isValidClassName(className)) {
        throw new ClassNotFoundException("Invalid class name: " + className);
      }
      return Global.getAnalysisCache()
          .getClassAnalysis(
              JavaClass.class,
              DescriptorFactory.instance()
                  .getClassDescriptor(ClassName.toSlashedClassName(className)));
    } catch (CheckedAnalysisException e) {
      throw new ClassNotFoundException("Class not found: " + className, e);
    }
  }

  public InnerClassAccessMap getInnerClassAccessMap() {
    return getDatabase(InnerClassAccessMap.class);
  }

  public void setAppClassList(List<ClassDescriptor> appClassCollection) {
    // FIXME: we really should drive the progress callback here
    HashSet<ClassDescriptor> appSet = new HashSet<ClassDescriptor>(appClassCollection);

    Collection<ClassDescriptor> allClassDescriptors =
        new ArrayList<ClassDescriptor>(DescriptorFactory.instance().getAllClassDescriptors());
    for (ClassDescriptor appClass : allClassDescriptors) {
      try {
        XClass xclass = currentXFactory().getXClass(appClass);

        if (xclass == null) {
          continue;
        }

        // Add the application class to the database
        if (appSet.contains(appClass)) {
          getSubtypes2().addApplicationClass(xclass);
        } else if (xclass instanceof ClassInfo) {
          getSubtypes2().addClass(xclass);
        }

      } catch (Exception e) {
        AnalysisContext.logError("Unable to get XClass for " + appClass, e);
      }
    }

    if (true && Subtypes2.DEBUG) {
      System.out.println(
          getSubtypes2().getGraph().getNumVertices() + " vertices in inheritance graph");
    }
  }

  /**
   * After a pass has been completed, allow the analysis context to update information.
   *
   * @param pass -- the first pass is pass 0
   */
  public void updateDatabases(int pass) {
    if (pass == 0) {
      getCheckReturnAnnotationDatabase().loadAuxiliaryAnnotations();
      getNullnessAnnotationDatabase().loadAuxiliaryAnnotations();
    }
  }

  /**
   * Get the property database recording which methods always return nonnull values
   *
   * @return the database, or null if there is no database available
   */
  public ReturnValueNullnessPropertyDatabase getReturnValueNullnessPropertyDatabase() {
    return getDatabase(ReturnValueNullnessPropertyDatabase.class);
  }

  /** Get the Subtypes2 inheritance hierarchy database. */
  public Subtypes2 getSubtypes2() {
    return Global.getAnalysisCache().getDatabase(Subtypes2.class);
  }

  public DirectlyRelevantTypeQualifiersDatabase getDirectlyRelevantTypeQualifiersDatabase() {
    return Global.getAnalysisCache().getDatabase(DirectlyRelevantTypeQualifiersDatabase.class);
  }

  @CheckForNull
  public XMethod getBridgeTo(MethodInfo m) {
    return bridgeTo.get(m);
  }

  @CheckForNull
  public XMethod getBridgeFrom(MethodInfo m) {
    return bridgeFrom.get(m);
  }

  public void setBridgeMethod(MethodInfo from, MethodInfo to) {
    bridgeTo.put(from, to);
    bridgeFrom.put(to, from);
  }

  public TypeQualifierNullnessAnnotationDatabase getNullnessAnnotationDatabase() {
    if (tqNullnessDatabase == null) {
      tqNullnessDatabase = new TypeQualifierNullnessAnnotationDatabase();
    }
    return tqNullnessDatabase;
  }

  protected <E> E getDatabase(Class<E> cls) {
    return Global.getAnalysisCache().getDatabase(cls);
  }

  static class DelegatingRepositoryLookupFailureCallback
      implements RepositoryLookupFailureCallback {

    @Override
    public void logError(String message) {
      Global.getAnalysisCache().getErrorLogger().logError(message);
    }

    @Override
    public void logError(String message, Throwable e) {
      Global.getAnalysisCache().getErrorLogger().logError(message, e);
    }

    @Override
    public void reportMissingClass(ClassNotFoundException ex) {
      Global.getAnalysisCache().getErrorLogger().reportMissingClass(ex);
    }

    @Override
    public void reportMissingClass(ClassDescriptor classDescriptor) {
      Global.getAnalysisCache().getErrorLogger().reportMissingClass(classDescriptor);
    }

    @Override
    public void reportSkippedAnalysis(MethodDescriptor method) {
      Global.getAnalysisCache().getErrorLogger().reportSkippedAnalysis(method);
    }
  }
}
Beispiel #2
0
/**
 * Analysis to determine where particular values are locked in a method. The dataflow values are
 * maps of value numbers to the number of times those values are locked.
 *
 * @author David Hovemeyer
 * @see ValueNumberAnalysis
 */
public class LockAnalysis extends ForwardDataflowAnalysis<LockSet> {
  private static final boolean DEBUG = SystemProperties.getBoolean("la.debug");

  private final MethodGen methodGen;

  private final ValueNumberDataflow vnaDataflow;

  private final ValueNumberAnalysis vna;

  private final boolean isSynchronized;

  private final boolean isStatic;

  public LockAnalysis(MethodGen methodGen, ValueNumberDataflow vnaDataflow, DepthFirstSearch dfs) {
    super(dfs);
    this.methodGen = methodGen;
    this.vnaDataflow = vnaDataflow;
    this.vna = vnaDataflow.getAnalysis();
    this.isSynchronized = methodGen.isSynchronized();
    this.isStatic = methodGen.isStatic();
    if (DEBUG) {
      System.out.println(
          "Analyzing Locks in " + methodGen.getClassName() + "." + methodGen.getName());
    }
  }

  @Override
  public LockSet createFact() {
    return new LockSet();
  }

  @Override
  public void copy(LockSet source, LockSet dest) {
    dest.copyFrom(source);
  }

  @Override
  public void initEntryFact(LockSet result) {
    result.clear();
    result.setDefaultLockCount(0);

    if (isSynchronized && !isStatic) {
      ValueNumber thisValue = vna.getThisValue();
      result.setLockCount(thisValue.getNumber(), 1);
    } else if (isSynchronized && isStatic) {
      ValueNumber thisValue = vna.getClassObjectValue(methodGen.getClassName());
      result.setLockCount(thisValue.getNumber(), 1);
    }
  }

  @Override
  public void makeFactTop(LockSet fact) {
    fact.clear();
    fact.setDefaultLockCount(LockSet.TOP);
  }

  @Override
  public boolean isTop(LockSet fact) {
    return fact.isTop();
  }

  @Override
  public boolean same(LockSet fact1, LockSet fact2) {
    return fact1.sameAs(fact2);
  }

  @Override
  public void meetInto(LockSet fact, Edge edge, LockSet result) throws DataflowAnalysisException {
    result.meetWith(fact);
  }

  @Override
  public void transferInstruction(InstructionHandle handle, BasicBlock basicBlock, LockSet fact)
      throws DataflowAnalysisException {

    Instruction ins = handle.getInstruction();
    short opcode = ins.getOpcode();
    if (opcode == Constants.MONITORENTER || opcode == Constants.MONITOREXIT) {
      ValueNumberFrame frame = vnaDataflow.getFactAtLocation(new Location(handle, basicBlock));

      modifyLock(frame, fact, opcode == Constants.MONITORENTER ? 1 : -1);

    } else if (opcode == Constants.INVOKEVIRTUAL || opcode == Constants.INVOKEINTERFACE) {

      InvokeInstruction inv = (InvokeInstruction) ins;
      String name = inv.getMethodName(methodGen.getConstantPool());
      String sig = inv.getSignature(methodGen.getConstantPool());
      ValueNumberFrame frame = vnaDataflow.getFactAtLocation(new Location(handle, basicBlock));

      if ("()V".equals(sig) && ("lock".equals(name) || "lockInterruptibly".equals(name))) {
        modifyLock(frame, fact, 1);
      } else if ("()V".equals(sig) && ("unlock".equals(name))) {
        modifyLock(frame, fact, -1);
      }

    } else if ((ins instanceof ReturnInstruction) && isSynchronized && !isStatic) {

      lockOp(fact, vna.getThisValue().getNumber(), -1);
    }
  }

  private void modifyLock(ValueNumberFrame frame, LockSet fact, int delta)
      throws DataflowAnalysisException {
    if (frame.isValid()) {
      int lockNumber = frame.getTopValue().getNumber();
      lockOp(fact, lockNumber, delta);
    }
  }

  private void lockOp(LockSet fact, int lockNumber, int delta) {
    int value = fact.getLockCount(lockNumber);
    if (value < 0) {
      return;
    }
    value += delta;
    if (value < 0) {
      value = LockSet.BOTTOM;
    }
    if (DEBUG) {
      System.out.println(
          "Setting "
              + lockNumber
              + " to "
              + value
              + " in "
              + methodGen.getClassName()
              + "."
              + methodGen.getName());
    }
    fact.setLockCount(lockNumber, value);
  }

  @Override
  public boolean isFactValid(LockSet fact) {
    return true;
  }

  // public static void main(String[] argv) throws Exception {
  // if (argv.length != 1) {
  // System.err.println("Usage: " + LockAnalysis.class.getName() +
  // " <classfile>");
  // System.exit(1);
  // }
  //
  // DataflowTestDriver<LockSet, LockAnalysis> driver = new
  // DataflowTestDriver<LockSet, LockAnalysis>() {
  // @Override
  // public Dataflow<LockSet, LockAnalysis> createDataflow(ClassContext
  // classContext, Method method)
  // throws CFGBuilderException, DataflowAnalysisException {
  // return classContext.getLockDataflow(method);
  // }
  // };
  //
  // driver.execute(argv[0]);
  // }
}
/**
 * Implementation of INullnessAnnotationDatabase that is based on JSR-305 type qualifiers.
 *
 * @author David Hovemeyer
 */
public class TypeQualifierNullnessAnnotationDatabase implements INullnessAnnotationDatabase {
  private static final boolean DEBUG = SystemProperties.getBoolean("findbugs.npe.tq.debug");

  public final TypeQualifierValue nonnullTypeQualifierValue;

  public TypeQualifierNullnessAnnotationDatabase() {
    ClassDescriptor nonnullClassDesc =
        DescriptorFactory.createClassDescriptor(javax.annotation.Nonnull.class);
    this.nonnullTypeQualifierValue = TypeQualifierValue.getValue(nonnullClassDesc, null);
  }

  /* (non-Javadoc)
   * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#getResolvedAnnotation(java.lang.Object, boolean)
   */
  public NullnessAnnotation getResolvedAnnotation(Object o, boolean getMinimal) {
    Profiler profiler = Global.getAnalysisCache().getProfiler();
    profiler.start(this.getClass());
    try {

      if (DEBUG) {
        System.out.println("getResolvedAnnotation: o=" + o + "...");
      }

      TypeQualifierAnnotation tqa = null;

      if (o instanceof XMethodParameter) {
        XMethodParameter param = (XMethodParameter) o;

        tqa =
            TypeQualifierApplications.getEffectiveTypeQualifierAnnotation(
                param.getMethod(), param.getParameterNumber(), nonnullTypeQualifierValue);
      } else if (o instanceof XMethod || o instanceof XField) {
        tqa =
            TypeQualifierApplications.getEffectiveTypeQualifierAnnotation(
                (AnnotatedObject) o, nonnullTypeQualifierValue);
      }

      NullnessAnnotation result = toNullnessAnnotation(tqa);
      if (DEBUG) {
        System.out.println("   ==> " + (result != null ? result.toString() : "not found"));
      }
      return result;
    } finally {
      profiler.end(this.getClass());
    }
  }

  /* (non-Javadoc)
   * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#parameterMustBeNonNull(edu.umd.cs.findbugs.ba.XMethod, int)
   */
  public boolean parameterMustBeNonNull(XMethod m, int param) {
    if (DEBUG) {
      System.out.print("Checking " + m + " param " + param + " for @Nonnull...");
    }
    TypeQualifierAnnotation tqa =
        TypeQualifierApplications.getEffectiveTypeQualifierAnnotation(
            m, param, nonnullTypeQualifierValue);

    if (tqa == null && param == 0) {
      String name = m.getName();
      String signature = m.getSignature();
      if (name.equals("main")
          && signature.equals("([Ljava/lang/String;)V")
          && m.isStatic()
          && m.isPublic()) return true;
      else if (NullnessAnnotationDatabase.assertsFirstParameterIsNonnull(m)) return true;
      else if (name.equals("compareTo")
          && signature.substring(signature.indexOf(";") + 1).equals(")Z")
          && !m.isStatic()) return true;
    }
    boolean answer = (tqa != null) && tqa.when == When.ALWAYS;

    if (DEBUG) {
      System.out.println(answer ? "yes" : "no");
    }

    return answer;
  }

  // NOTE:
  // The way we handle adding default annotations is to actually add AnnotationValues
  // to the corresponding XFoo objects, giving the illusion that the annotations
  // were actually read from the underlying class files.

  /**
   * Convert a NullnessAnnotation into the ClassDescriptor of the equivalent JSR-305 nullness type
   * qualifier.
   *
   * @param n a NullnessAnnotation
   * @return ClassDescriptor of the equivalent JSR-305 nullness type qualifier
   */
  private ClassDescriptor getNullnessAnnotationClassDescriptor(NullnessAnnotation n) {
    if (n == NullnessAnnotation.CHECK_FOR_NULL) {
      return JSR305NullnessAnnotations.CHECK_FOR_NULL;
    } else if (n == NullnessAnnotation.NONNULL) {
      return JSR305NullnessAnnotations.NONNULL;
    } else if (n == NullnessAnnotation.NULLABLE) {
      return JSR305NullnessAnnotations.NULLABLE;
    } else if (n == NullnessAnnotation.UNKNOWN_NULLNESS) {
      return JSR305NullnessAnnotations.NULLABLE;
    } else {
      throw new IllegalArgumentException("Unknown NullnessAnnotation: " + n);
    }
  }

  private static final ClassDescriptor PARAMETERS_ARE_NONNULL_BY_DEFAULT =
      DescriptorFactory.createClassDescriptor(javax.annotation.ParametersAreNonnullByDefault.class);
  private static final ClassDescriptor RETURN_VALUES_ARE_NONNULL_BY_DEFAULT =
      DescriptorFactory.createClassDescriptor(
          edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault.class);
  /* (non-Javadoc)
   * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addDefaultAnnotation(java.lang.String, java.lang.String, edu.umd.cs.findbugs.ba.NullnessAnnotation)
   */
  public void addDefaultAnnotation(Target target, String c, NullnessAnnotation n) {
    if (DEBUG) {
      System.out.println("addDefaultAnnotation: target=" + target + ", c=" + c + ", n=" + n);
    }

    ClassDescriptor classDesc =
        DescriptorFactory.instance().getClassDescriptorForDottedClassName(c);
    ClassInfo xclass;

    // Get the XClass (really a ClassInfo object)
    try {
      xclass = (ClassInfo) Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc);
    } catch (MissingClassException e) {
      //
      //	AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e.getClassDescriptor());
      return;
    } catch (CheckedAnalysisException e) {
      //			AnalysisContext.logError("Error adding built-in nullness annotation", e);
      return;
    }
    if (n == NullnessAnnotation.NONNULL && target == AnnotationDatabase.Target.PARAMETER) {
      xclass.addAnnotation(new AnnotationValue(PARAMETERS_ARE_NONNULL_BY_DEFAULT));
      return;
    } else if (n == NullnessAnnotation.NONNULL && target == AnnotationDatabase.Target.METHOD) {
      xclass.addAnnotation(new AnnotationValue(RETURN_VALUES_ARE_NONNULL_BY_DEFAULT));
      return;
    }
    // Get the default annotation type
    ClassDescriptor defaultAnnotationType;
    if (target == AnnotationDatabase.Target.ANY) {
      defaultAnnotationType = FindBugsDefaultAnnotations.DEFAULT_ANNOTATION;
    } else if (target == AnnotationDatabase.Target.FIELD) {
      defaultAnnotationType = FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_FIELDS;
    } else if (target == AnnotationDatabase.Target.METHOD) {
      defaultAnnotationType = FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_METHODS;
    } else if (target == AnnotationDatabase.Target.PARAMETER) {
      defaultAnnotationType = FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_PARAMETERS;
    } else {
      throw new IllegalArgumentException("Unknown target for default annotation: " + target);
    }

    // Get the JSR-305 nullness annotation type
    ClassDescriptor nullnessAnnotationType = getNullnessAnnotationClassDescriptor(n);

    // Construct an AnnotationValue containing the default annotation
    AnnotationValue annotationValue = new AnnotationValue(defaultAnnotationType);
    AnnotationVisitor v = annotationValue.getAnnotationVisitor();
    v.visit("value", Type.getObjectType(nullnessAnnotationType.getClassName()));
    v.visitEnd();

    if (DEBUG) {
      System.out.println("Adding AnnotationValue " + annotationValue + " to class " + xclass);
    }

    // Destructively add the annotation to the ClassInfo object
    xclass.addAnnotation(annotationValue);
  }

  //	/* (non-Javadoc)
  //	 * @see
  // edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addDefaultMethodAnnotation(java.lang.String,
  // edu.umd.cs.findbugs.ba.NullnessAnnotation)
  //	 */
  //	public void addDefaultMethodAnnotation(String name, NullnessAnnotation annotation) {
  //	}

  /* (non-Javadoc)
   * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addFieldAnnotation(java.lang.String, java.lang.String, java.lang.String, boolean, edu.umd.cs.findbugs.ba.NullnessAnnotation)
   */
  public void addFieldAnnotation(
      String cName, String mName, String mSig, boolean isStatic, NullnessAnnotation annotation) {
    if (DEBUG) {
      System.out.println(
          "addFieldAnnotation: annotate " + cName + "." + mName + " with " + annotation);
    }

    XField xfield = XFactory.createXField(cName, mName, mSig, isStatic);
    if (!(xfield instanceof FieldInfo)) {
      if (DEBUG) {
        System.out.println(
            "  Field not found! "
                + cName
                + "."
                + mName
                + ":"
                + mSig
                + " "
                + isStatic
                + " "
                + annotation);
      }
      return;
    }

    // Get JSR-305 nullness annotation type
    ClassDescriptor nullnessAnnotationType = getNullnessAnnotationClassDescriptor(annotation);

    // Create an AnnotationValue
    AnnotationValue annotationValue = new AnnotationValue(nullnessAnnotationType);

    // Destructively add the annotation to the FieldInfo object
    ((FieldInfo) xfield).addAnnotation(annotationValue);
  }

  public @CheckForNull XMethod getXMethod(
      String cName, String mName, String sig, boolean isStatic) {
    ClassDescriptor classDesc =
        DescriptorFactory.instance().getClassDescriptorForDottedClassName(cName);
    ClassInfo xclass;

    // Get the XClass (really a ClassInfo object)
    try {
      xclass = (ClassInfo) Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc);
    } catch (MissingClassException e) {
      if (DEBUG) {
        System.out.println("  Class not found!");
      }
      //
      //	AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e.getClassDescriptor());
      return null;
    } catch (CheckedAnalysisException e) {
      if (DEBUG) {
        System.out.println("  Class not found!");
      }
      //			AnalysisContext.logError("Error adding built-in nullness annotation", e);
      return null;
    }
    XMethod xmethod = xclass.findMethod(mName, sig, isStatic);

    if (xmethod == null) xmethod = XFactory.createXMethod(cName, mName, sig, isStatic);
    return xmethod;
  }
  /* (non-Javadoc)
   * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addMethodAnnotation(java.lang.String, java.lang.String, java.lang.String, boolean, edu.umd.cs.findbugs.ba.NullnessAnnotation)
   */
  public void addMethodAnnotation(
      String cName, String mName, String sig, boolean isStatic, NullnessAnnotation annotation) {
    if (DEBUG) {
      System.out.println(
          "addMethodAnnotation: annotate " + cName + "." + mName + " with " + annotation);
    }
    XMethod xmethod = getXMethod(cName, mName, sig, isStatic);
    if (xmethod == null) return;
    // Get JSR-305 nullness annotation type
    ClassDescriptor nullnessAnnotationType = getNullnessAnnotationClassDescriptor(annotation);

    // Create an AnnotationValue
    AnnotationValue annotationValue = new AnnotationValue(nullnessAnnotationType);

    // Destructively add the annotation to the MethodInfo object
    xmethod.addAnnotation(annotationValue);
  }

  /* (non-Javadoc)
   * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addMethodParameterAnnotation(java.lang.String, java.lang.String, java.lang.String, boolean, int, edu.umd.cs.findbugs.ba.NullnessAnnotation)
   */
  public void addMethodParameterAnnotation(
      @DottedClassName String cName,
      String mName,
      String sig,
      boolean isStatic,
      int param,
      NullnessAnnotation annotation) {
    if (DEBUG) {
      System.out.println(
          "addMethodParameterAnnotation: annotate "
              + cName
              + "."
              + mName
              + " param "
              + param
              + " with "
              + annotation);
    }
    XMethod xmethod = getXMethod(cName, mName, sig, isStatic);
    if (xmethod == null) return;
    // Get JSR-305 nullness annotation type
    ClassDescriptor nullnessAnnotationType = getNullnessAnnotationClassDescriptor(annotation);

    // Create an AnnotationValue
    AnnotationValue annotationValue = new AnnotationValue(nullnessAnnotationType);

    if (!xmethod.getClassName().equals(cName)) {
      if (false)
        AnalysisContext.logError(
            "Could not fully resolve method "
                + cName
                + "."
                + mName
                + sig
                + " to apply annotation "
                + annotation);
      return;
    }

    // Destructively add the annotation to the MethodInfo object
    xmethod.addParameterAnnotation(param, annotationValue);
  }

  /* (non-Javadoc)
   * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#loadAuxiliaryAnnotations()
   */
  public void loadAuxiliaryAnnotations() {
    DefaultNullnessAnnotations.addDefaultNullnessAnnotations(this);
  }

  /**
   * Convert a Nonnull-based TypeQualifierAnnotation into a NullnessAnnotation.
   *
   * @param tqa Nonnull-based TypeQualifierAnnotation
   * @return corresponding NullnessAnnotation
   */
  private NullnessAnnotation toNullnessAnnotation(@CheckForNull TypeQualifierAnnotation tqa) {
    if (tqa == null) {
      return null;
    }

    switch (tqa.when) {
      case ALWAYS:
        return NullnessAnnotation.NONNULL;
      case MAYBE:
        return NullnessAnnotation.CHECK_FOR_NULL;
      case NEVER:
        return NullnessAnnotation.CHECK_FOR_NULL;
      case UNKNOWN:
        return NullnessAnnotation.UNKNOWN_NULLNESS;
    }

    throw new IllegalStateException();
  }
}
/**
 * Facade for class hierarchy queries. These typically access the class hierarchy using the {@link
 * org.apache.bcel.Repository} class. Callers should generally expect to handle
 * ClassNotFoundException for when referenced classes can't be found.
 *
 * @author David Hovemeyer
 */
public class Hierarchy {
  protected static final boolean DEBUG_METHOD_LOOKUP =
      SystemProperties.getBoolean("hier.lookup.debug");

  /** Type of java.lang.Exception. */
  public static final ObjectType EXCEPTION_TYPE =
      ObjectTypeFactory.getInstance("java.lang.Exception");
  /** Type of java.lang.Error. */
  public static final ObjectType ERROR_TYPE = ObjectTypeFactory.getInstance("java.lang.Error");
  /** Type of java.lang.RuntimeException. */
  public static final ObjectType RUNTIME_EXCEPTION_TYPE =
      ObjectTypeFactory.getInstance("java.lang.RuntimeException");

  /**
   * Determine whether one class (or reference type) is a subtype of another.
   *
   * @param clsName the name of the class or reference type
   * @param possibleSupertypeClassName the name of the possible superclass
   * @return true if clsName is a subtype of possibleSupertypeClassName, false if not
   */
  public static boolean isSubtype(String clsName, String possibleSupertypeClassName)
      throws ClassNotFoundException {
    ObjectType cls = ObjectTypeFactory.getInstance(clsName);
    ObjectType superCls = ObjectTypeFactory.getInstance(possibleSupertypeClassName);
    return isSubtype(cls, superCls);
  }

  /**
   * Determine if one reference type is a subtype of another.
   *
   * @param t a reference type
   * @param possibleSupertype the possible supertype
   * @return true if t is a subtype of possibleSupertype, false if not
   */
  public static boolean isSubtype(ReferenceType t, ReferenceType possibleSupertype)
      throws ClassNotFoundException {
    if (Subtypes2.ENABLE_SUBTYPES2) {
      return Global.getAnalysisCache().getDatabase(Subtypes2.class).isSubtype(t, possibleSupertype);
    } else {
      Map<ReferenceType, Boolean> subtypes = subtypeCache.get(possibleSupertype);
      if (subtypes == null) {
        subtypes = new HashMap<ReferenceType, Boolean>();
        subtypeCache.put(possibleSupertype, subtypes);
      }
      Boolean result = subtypes.get(t);
      if (result == null) {
        result = Boolean.valueOf(t.isAssignmentCompatibleWith(possibleSupertype));
        subtypes.put(t, result);
      }
      return result;
    }
  }

  static Map<ReferenceType, Map<ReferenceType, Boolean>> subtypeCache =
      new HashMap<ReferenceType, Map<ReferenceType, Boolean>>();
  /**
   * Determine if the given ObjectType reference represents a <em>universal</em> exception handler.
   * That is, one that will catch any kind of exception.
   *
   * @param catchType the ObjectType of the exception handler
   * @return true if catchType is null, or if catchType is java.lang.Throwable
   */
  public static boolean isUniversalExceptionHandler(ObjectType catchType) {
    return catchType == null || catchType.equals(Type.THROWABLE);
  }

  /**
   * Determine if the given ObjectType refers to an unchecked exception (RuntimeException or Error).
   */
  public static boolean isUncheckedException(ObjectType type) throws ClassNotFoundException {
    return isSubtype(type, RUNTIME_EXCEPTION_TYPE)
        || isSubtype(type, ERROR_TYPE)
        || type.equals(Type.THROWABLE);
  }

  /**
   * Determine if method whose name and signature is specified is a monitor wait operation.
   *
   * @param methodName name of the method
   * @param methodSig signature of the method
   * @return true if the method is a monitor wait, false if not
   */
  public static boolean isMonitorWait(String methodName, String methodSig) {
    return methodName.equals("wait")
        && (methodSig.equals("()V") || methodSig.equals("(J)V") || methodSig.equals("(JI)V"));
  }

  /**
   * Determine if given Instruction is a monitor wait.
   *
   * @param ins the Instruction
   * @param cpg the ConstantPoolGen for the Instruction
   * @return true if the instruction is a monitor wait, false if not
   */
  public static boolean isMonitorWait(Instruction ins, ConstantPoolGen cpg) {
    if (!(ins instanceof InvokeInstruction)) return false;
    if (ins.getOpcode() == Constants.INVOKESTATIC) return false;

    InvokeInstruction inv = (InvokeInstruction) ins;
    String methodName = inv.getMethodName(cpg);
    String methodSig = inv.getSignature(cpg);

    return isMonitorWait(methodName, methodSig);
  }

  /**
   * Determine if method whose name and signature is specified is a monitor notify operation.
   *
   * @param methodName name of the method
   * @param methodSig signature of the method
   * @return true if the method is a monitor notify, false if not
   */
  public static boolean isMonitorNotify(String methodName, String methodSig) {
    return (methodName.equals("notify") || methodName.equals("notifyAll"))
        && methodSig.equals("()V");
  }
  /**
   * Determine if given Instruction is a monitor wait.
   *
   * @param ins the Instruction
   * @param cpg the ConstantPoolGen for the Instruction
   * @return true if the instruction is a monitor wait, false if not
   */
  public static boolean isMonitorNotify(Instruction ins, ConstantPoolGen cpg) {
    if (!(ins instanceof InvokeInstruction)) return false;
    if (ins.getOpcode() == Constants.INVOKESTATIC) return false;

    InvokeInstruction inv = (InvokeInstruction) ins;
    String methodName = inv.getMethodName(cpg);
    String methodSig = inv.getSignature(cpg);

    return isMonitorNotify(methodName, methodSig);
  }

  /**
   * Look up the method referenced by given InvokeInstruction. This method does <em>not</em> look
   * for implementations in super or subclasses according to the virtual dispatch rules.
   *
   * @param inv the InvokeInstruction
   * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to
   * @return the JavaClassAndMethod, or null if no such method is defined in the class
   */
  public static JavaClassAndMethod findExactMethod(InvokeInstruction inv, ConstantPoolGen cpg)
      throws ClassNotFoundException {
    return findExactMethod(inv, cpg, ANY_METHOD);
  }

  /**
   * Look up the method referenced by given InvokeInstruction. This method does <em>not</em> look
   * for implementations in super or subclasses according to the virtual dispatch rules.
   *
   * @param inv the InvokeInstruction
   * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to
   * @param chooser JavaClassAndMethodChooser to use to pick the method from among the candidates
   * @return the JavaClassAndMethod, or null if no such method is defined in the class
   */
  public static JavaClassAndMethod findExactMethod(
      InvokeInstruction inv, ConstantPoolGen cpg, JavaClassAndMethodChooser chooser)
      throws ClassNotFoundException {
    String className = inv.getClassName(cpg);
    String methodName = inv.getName(cpg);
    String methodSig = inv.getSignature(cpg);

    JavaClass jclass = Repository.lookupClass(className);
    return findMethod(jclass, methodName, methodSig, chooser);
  }

  /**
   * Visit all superclass methods which the given method overrides.
   *
   * @param method the method
   * @param chooser chooser which visits each superclass method
   * @return the chosen method, or null if no method is chosen
   * @throws ClassNotFoundException
   */
  public static JavaClassAndMethod visitSuperClassMethods(
      JavaClassAndMethod method, JavaClassAndMethodChooser chooser) throws ClassNotFoundException {
    return findMethod(
        method.getJavaClass().getSuperClasses(),
        method.getMethod().getName(),
        method.getMethod().getSignature(),
        chooser);
  }

  /**
   * Visit all superinterface methods which the given method implements.
   *
   * @param method the method
   * @param chooser chooser which visits each superinterface method
   * @return the chosen method, or null if no method is chosen
   * @throws ClassNotFoundException
   */
  public static JavaClassAndMethod visitSuperInterfaceMethods(
      JavaClassAndMethod method, JavaClassAndMethodChooser chooser) throws ClassNotFoundException {
    return findMethod(
        method.getJavaClass().getAllInterfaces(),
        method.getMethod().getName(),
        method.getMethod().getSignature(),
        chooser);
  }

  /**
   * Find the least upper bound method in the class hierarchy which could be called by the given
   * InvokeInstruction. One reason this method is useful is that it indicates which declared
   * exceptions are thrown by the called methods.
   *
   * <p>
   *
   * <ul>
   *   <li>For invokespecial, this is simply an exact lookup.
   *   <li>For invokestatic and invokevirtual, the named class is searched, followed by superclasses
   *       up to the root of the object hierarchy (java.lang.Object). Yes, invokestatic really is
   *       declared to check superclasses. See VMSpec, 2nd ed, sec. 5.4.3.3.
   *   <li>For invokeinterface, the named class is searched, followed by all interfaces transitively
   *       declared by the class. (Question: is the order important here? Maybe the VM spec requires
   *       that the actual interface desired is given, so the extended lookup will not be required.
   *       Should check.)
   * </ul>
   *
   * @param inv the InvokeInstruction
   * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to
   * @return the JavaClassAndMethod, or null if no matching method can be found
   */
  public static @CheckForNull JavaClassAndMethod findInvocationLeastUpperBound(
      InvokeInstruction inv, ConstantPoolGen cpg) throws ClassNotFoundException {
    return findInvocationLeastUpperBound(inv, cpg, ANY_METHOD);
  }

  public static @CheckForNull JavaClassAndMethod findInvocationLeastUpperBound(
      InvokeInstruction inv, ConstantPoolGen cpg, JavaClassAndMethodChooser methodChooser)
      throws ClassNotFoundException {

    if (DEBUG_METHOD_LOOKUP) {
      System.out.println(
          "Find prototype method for " + SignatureConverter.convertMethodSignature(inv, cpg));
    }

    short opcode = inv.getOpcode();

    if (opcode == Constants.INVOKESTATIC) {
      if (methodChooser == INSTANCE_METHOD) return null;
    } else {
      if (methodChooser == STATIC_METHOD) return null;
    }

    // Find the method
    if (opcode == Constants.INVOKESPECIAL) {
      // Non-virtual dispatch
      return findExactMethod(inv, cpg, methodChooser);
    } else {
      String className = inv.getClassName(cpg);
      String methodName = inv.getName(cpg);
      String methodSig = inv.getSignature(cpg);
      if (DEBUG_METHOD_LOOKUP) {
        System.out.println("[Class name is " + className + "]");
        System.out.println("[Method name is " + methodName + "]");
        System.out.println("[Method signature is " + methodSig + "]");
      }

      if (className.startsWith("[")) {
        // Java 1.5 allows array classes to appear as the class name
        className = "java.lang.Object";
      }

      JavaClass jClass = Repository.lookupClass(className);
      return findInvocationLeastUpperBound(
          jClass, methodName, methodSig, methodChooser, opcode == Constants.INVOKEINTERFACE);
    }
  }

  public static @CheckForNull JavaClassAndMethod findInvocationLeastUpperBound(
      JavaClass jClass,
      String methodName,
      String methodSig,
      JavaClassAndMethodChooser methodChooser,
      boolean invokeInterface)
      throws ClassNotFoundException {
    JavaClassAndMethod result = findMethod(jClass, methodName, methodSig, methodChooser);
    if (result != null) return result;
    if (invokeInterface)
      for (JavaClass i : jClass.getInterfaces()) {
        result =
            findInvocationLeastUpperBound(i, methodName, methodSig, methodChooser, invokeInterface);
        if (result != null) return null;
      }
    else {
      JavaClass sClass = jClass.getSuperClass();
      if (sClass != null)
        return findInvocationLeastUpperBound(
            sClass, methodName, methodSig, methodChooser, invokeInterface);
    }
    return null;
  }
  /**
   * Find the declared exceptions for the method called by given instruction.
   *
   * @param inv the InvokeInstruction
   * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to
   * @return array of ObjectTypes of thrown exceptions, or null if we can't find the list of
   *     declared exceptions
   * @deprecated Use {@link Hierarchy2#findDeclaredExceptions(InvokeInstruction,ConstantPoolGen)}
   *     instead
   */
  @Deprecated
  public static ObjectType[] findDeclaredExceptions(InvokeInstruction inv, ConstantPoolGen cpg)
      throws ClassNotFoundException {
    return Hierarchy2.findDeclaredExceptions(inv, cpg);
  }

  /**
   * Find a method in given class.
   *
   * @param javaClass the class
   * @param methodName the name of the method
   * @param methodSig the signature of the method
   * @return the JavaClassAndMethod, or null if no such method exists in the class
   */
  public static @CheckForNull JavaClassAndMethod findMethod(
      JavaClass javaClass, String methodName, String methodSig) {
    return findMethod(javaClass, methodName, methodSig, ANY_METHOD);
  }

  public static @CheckForNull JavaClassAndMethod findMethod(
      JavaClass javaClass, String methodName, String methodSig, JavaClassAndMethodChooser chooser) {
    if (DEBUG_METHOD_LOOKUP) {
      System.out.println("Check " + javaClass.getClassName());
    }
    Method[] methodList = javaClass.getMethods();
    for (Method method : methodList)
      if (method.getName().equals(methodName) && method.getSignature().equals(methodSig)) {
        JavaClassAndMethod m = new JavaClassAndMethod(javaClass, method);
        if (chooser.choose(m)) {
          if (DEBUG_METHOD_LOOKUP) {
            System.out.println("\t==> FOUND: " + method);
          }
          return m;
        }
      }
    if (DEBUG_METHOD_LOOKUP) {
      System.out.println("\t==> NOT FOUND");
    }
    return null;
  }

  /**
   * Find a method in given class.
   *
   * @param classDesc the class descriptor
   * @param methodName the name of the method
   * @param methodSig the signature of the method
   * @param isStatic are we looking for a static method?
   * @return the JavaClassAndMethod, or null if no such method exists in the class
   */
  public static @CheckForNull XMethod findMethod(
      ClassDescriptor classDesc, String methodName, String methodSig, boolean isStatic) {
    if (DEBUG_METHOD_LOOKUP) {
      System.out.println("Check " + classDesc.getClassName());
    }

    try {
      XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc);
      return xClass.findMethod(methodName, methodSig, isStatic);
    } catch (CheckedAnalysisException e) {
      AnalysisContext.logError("Error looking for " + classDesc + "." + methodName + methodSig, e);
      return null;
    }
  }

  /**
   * Find a method in given class.
   *
   * @param javaClass the class
   * @param methodName the name of the method
   * @param methodSig the signature of the method
   * @return the JavaClassAndMethod, or null if no such method exists in the class
   */
  @Deprecated
  public static @CheckForNull JavaClassAndMethod findConcreteMethod(
      JavaClass javaClass, String methodName, String methodSig) {

    if (DEBUG_METHOD_LOOKUP) {
      System.out.println("Check " + javaClass.getClassName());
    }
    Method[] methodList = javaClass.getMethods();
    for (Method method : methodList)
      if (method.getName().equals(methodName)
          && method.getSignature().equals(methodSig)
          && accessFlagsAreConcrete(method.getAccessFlags())) {
        JavaClassAndMethod m = new JavaClassAndMethod(javaClass, method);

        return m;
      }
    if (DEBUG_METHOD_LOOKUP) {
      System.out.println("\t==> NOT FOUND");
    }
    return null;
  }

  /**
   * Find a method in given class.
   *
   * @param javaClass the class
   * @param methodName the name of the method
   * @param methodSig the signature of the method
   * @param chooser the JavaClassAndMethodChooser to use to screen possible candidates
   * @return the XMethod, or null if no such method exists in the class
   */
  @Deprecated
  public static @CheckForNull XMethod findXMethod(
      JavaClass javaClass, String methodName, String methodSig, JavaClassAndMethodChooser chooser) {
    JavaClassAndMethod result = findMethod(javaClass, methodName, methodSig, chooser);
    return result == null
        ? null
        : XFactory.createXMethod(result.getJavaClass(), result.getMethod());
  }

  /** JavaClassAndMethodChooser which accepts any method. */
  public static final JavaClassAndMethodChooser ANY_METHOD =
      new JavaClassAndMethodChooser() {
        public boolean choose(JavaClassAndMethod javaClassAndMethod) {
          return true;
        }

        public boolean choose(XMethod method) {
          return true;
        }
      };

  // FIXME: perhaps native methods should be concrete.
  public static boolean accessFlagsAreConcrete(int accessFlags) {
    return (accessFlags & Constants.ACC_ABSTRACT) == 0 && (accessFlags & Constants.ACC_NATIVE) == 0;
  }

  /** JavaClassAndMethodChooser which accepts only concrete (not abstract or native) methods. */
  public static final JavaClassAndMethodChooser CONCRETE_METHOD =
      new JavaClassAndMethodChooser() {
        public boolean choose(JavaClassAndMethod javaClassAndMethod) {
          Method method = javaClassAndMethod.getMethod();
          int accessFlags = method.getAccessFlags();
          return accessFlagsAreConcrete(accessFlags);
        }

        public boolean choose(XMethod method) {
          return accessFlagsAreConcrete(method.getAccessFlags());
        }
      };

  /** JavaClassAndMethodChooser which accepts only static methods. */
  public static final JavaClassAndMethodChooser STATIC_METHOD =
      new JavaClassAndMethodChooser() {
        public boolean choose(JavaClassAndMethod javaClassAndMethod) {
          return javaClassAndMethod.getMethod().isStatic();
        }

        public boolean choose(XMethod method) {
          return method.isStatic();
        }
      };

  /** JavaClassAndMethodChooser which accepts only instance methods. */
  public static final JavaClassAndMethodChooser INSTANCE_METHOD =
      new JavaClassAndMethodChooser() {
        public boolean choose(JavaClassAndMethod javaClassAndMethod) {
          return !javaClassAndMethod.getMethod().isStatic();
        }

        public boolean choose(XMethod method) {
          return !method.isStatic();
        }
      };

  /**
   * Find a method in given list of classes, searching the classes in order.
   *
   * @param classList list of classes in which to search
   * @param methodName the name of the method
   * @param methodSig the signature of the method
   * @return the JavaClassAndMethod, or null if no such method exists in the class
   */
  @Deprecated
  public static JavaClassAndMethod findMethod(
      JavaClass[] classList, String methodName, String methodSig) {
    return findMethod(classList, methodName, methodSig, ANY_METHOD);
  }

  /**
   * Find a method in given list of classes, searching the classes in order.
   *
   * @param classList list of classes in which to search
   * @param methodName the name of the method
   * @param methodSig the signature of the method
   * @param chooser JavaClassAndMethodChooser to select which methods are considered; it must return
   *     true for a method to be returned
   * @return the JavaClassAndMethod, or null if no such method exists in the class
   */
  public static JavaClassAndMethod findMethod(
      JavaClass[] classList,
      String methodName,
      String methodSig,
      JavaClassAndMethodChooser chooser) {
    JavaClassAndMethod m = null;

    for (JavaClass cls : classList) {
      if ((m = findMethod(cls, methodName, methodSig, chooser)) != null) break;
    }

    return m;
  }

  /**
   * Find XMethod for method in given list of classes, searching the classes in order.
   *
   * @param classList list of classes in which to search
   * @param methodName the name of the method
   * @param methodSig the signature of the method
   * @return the XMethod, or null if no such method exists in the class
   */
  @Deprecated
  public static XMethod findXMethod(JavaClass[] classList, String methodName, String methodSig) {
    return findXMethod(classList, methodName, methodSig, ANY_METHOD);
  }

  /**
   * Find XMethod for method in given list of classes, searching the classes in order.
   *
   * @param classList list of classes in which to search
   * @param methodName the name of the method
   * @param methodSig the signature of the method
   * @param chooser JavaClassAndMethodChooser to select which methods are considered; it must return
   *     true for a method to be returned
   * @return the XMethod, or null if no such method exists in the class
   */
  @Deprecated
  public static XMethod findXMethod(
      JavaClass[] classList,
      String methodName,
      String methodSig,
      JavaClassAndMethodChooser chooser) {
    for (JavaClass cls : classList) {
      JavaClassAndMethod m;
      if ((m = findMethod(cls, methodName, methodSig)) != null && chooser.choose(m)) {
        return XFactory.createXMethod(cls, m.getMethod());
      }
    }
    return null;
  }

  /**
   * Resolve possible method call targets. This works for both static and instance method calls.
   *
   * @param invokeInstruction the InvokeInstruction
   * @param typeFrame the TypeFrame containing the types of stack values
   * @param cpg the ConstantPoolGen
   * @return Set of methods which might be called
   * @throws DataflowAnalysisException
   * @throws ClassNotFoundException
   */
  public static Set<JavaClassAndMethod> resolveMethodCallTargets(
      InvokeInstruction invokeInstruction, TypeFrame typeFrame, ConstantPoolGen cpg)
      throws DataflowAnalysisException, ClassNotFoundException {

    short opcode = invokeInstruction.getOpcode();

    if (opcode == Constants.INVOKESTATIC) {
      HashSet<JavaClassAndMethod> result = new HashSet<JavaClassAndMethod>();
      JavaClassAndMethod targetMethod =
          findInvocationLeastUpperBound(invokeInstruction, cpg, CONCRETE_METHOD);
      if (targetMethod != null) {
        result.add(targetMethod);
      }
      return result;
    }

    if (!typeFrame.isValid()) {
      return new HashSet<JavaClassAndMethod>();
    }

    Type receiverType;
    boolean receiverTypeIsExact;

    if (opcode == Constants.INVOKESPECIAL) {
      // invokespecial instructions are dispatched to EXACTLY
      // the class specified by the instruction
      receiverType = ObjectTypeFactory.getInstance(invokeInstruction.getClassName(cpg));
      receiverTypeIsExact = false; // Doesn't actually matter
    } else {
      // For invokevirtual and invokeinterface instructions, we have
      // virtual dispatch.  By taking the receiver type (which may be a
      // subtype of the class specified by the instruction),
      // we may get a more precise set of call targets.
      int instanceStackLocation = typeFrame.getInstanceStackLocation(invokeInstruction, cpg);
      receiverType = typeFrame.getStackValue(instanceStackLocation);
      if (!(receiverType instanceof ReferenceType)) {
        return new HashSet<JavaClassAndMethod>();
      }
      receiverTypeIsExact = typeFrame.isExact(instanceStackLocation);
    }
    if (DEBUG_METHOD_LOOKUP) {
      System.out.println(
          "[receiver type is "
              + receiverType
              + ", "
              + (receiverTypeIsExact ? "exact]" : " not exact]"));
    }

    return resolveMethodCallTargets(
        (ReferenceType) receiverType, invokeInstruction, cpg, receiverTypeIsExact);
  }

  /**
   * Resolve possible instance method call targets. Assumes that invokevirtual and invokeinterface
   * methods may call any subtype of the receiver class.
   *
   * @param receiverType type of the receiver object
   * @param invokeInstruction the InvokeInstruction
   * @param cpg the ConstantPoolGen
   * @return Set of methods which might be called
   * @throws ClassNotFoundException
   */
  public static Set<JavaClassAndMethod> resolveMethodCallTargets(
      ReferenceType receiverType, InvokeInstruction invokeInstruction, ConstantPoolGen cpg)
      throws ClassNotFoundException {
    return resolveMethodCallTargets(receiverType, invokeInstruction, cpg, false);
  }

  /**
   * Resolve possible instance method call targets.
   *
   * @param receiverType type of the receiver object
   * @param invokeInstruction the InvokeInstruction
   * @param cpg the ConstantPoolGen
   * @param receiverTypeIsExact if true, the receiver type is known exactly, which should allow a
   *     precise result
   * @return Set of methods which might be called
   * @throws ClassNotFoundException
   */
  public static Set<JavaClassAndMethod> resolveMethodCallTargets(
      ReferenceType receiverType,
      InvokeInstruction invokeInstruction,
      ConstantPoolGen cpg,
      boolean receiverTypeIsExact)
      throws ClassNotFoundException {
    HashSet<JavaClassAndMethod> result = new HashSet<JavaClassAndMethod>();

    if (invokeInstruction.getOpcode() == Constants.INVOKESTATIC)
      throw new IllegalArgumentException();

    String methodName = invokeInstruction.getName(cpg);
    String methodSig = invokeInstruction.getSignature(cpg);

    // Array method calls aren't virtual.
    // They should just resolve to Object methods.
    if (receiverType instanceof ArrayType) {
      JavaClass javaLangObject =
          AnalysisContext.currentAnalysisContext().lookupClass("java.lang.Object");
      JavaClassAndMethod classAndMethod =
          findMethod(javaLangObject, methodName, methodSig, INSTANCE_METHOD);
      if (classAndMethod != null) result.add(classAndMethod);
      return result;
    }

    if (receiverType instanceof NullType) {
      return Collections.<JavaClassAndMethod>emptySet();
    }
    AnalysisContext analysisContext = AnalysisContext.currentAnalysisContext();

    // Get the receiver class.
    String receiverClassName = ((ObjectType) receiverType).getClassName();
    JavaClass receiverClass = analysisContext.lookupClass(receiverClassName);
    ClassDescriptor receiverDesc =
        DescriptorFactory.createClassDescriptorFromDottedClassName(receiverClassName);

    // Figure out the upper bound for the method.
    // This is what will be called if this is not a virtual call site.
    JavaClassAndMethod upperBound =
        findMethod(receiverClass, methodName, methodSig, CONCRETE_METHOD);
    if (upperBound == null) {
      upperBound =
          findInvocationLeastUpperBound(
              receiverClass, methodName, methodSig, CONCRETE_METHOD, false);
    }
    if (upperBound != null) {
      if (DEBUG_METHOD_LOOKUP) {
        System.out.println(
            "Adding upper bound: "
                + SignatureConverter.convertMethodSignature(
                    upperBound.getJavaClass(), upperBound.getMethod()));
      }
      result.add(upperBound);
    }

    // Is this a virtual call site?
    boolean virtualCall =
        (invokeInstruction.getOpcode() == Constants.INVOKEVIRTUAL
                || invokeInstruction.getOpcode() == Constants.INVOKEINTERFACE)
            && (upperBound == null
                || !upperBound.getJavaClass().isFinal() && !upperBound.getMethod().isFinal())
            && !receiverTypeIsExact;

    if (virtualCall) {
      if (!receiverClassName.equals("java.lang.Object")) {

        // This is a true virtual call: assume that any concrete
        // subtype method may be called.
        Set<ClassDescriptor> subTypeSet = analysisContext.getSubtypes2().getSubtypes(receiverDesc);
        for (ClassDescriptor subtype : subTypeSet) {
          XMethod concreteSubtypeMethod = findMethod(subtype, methodName, methodSig, false);
          if (concreteSubtypeMethod != null
              && (concreteSubtypeMethod.getAccessFlags() & Constants.ACC_ABSTRACT) == 0) {
            result.add(new JavaClassAndMethod(concreteSubtypeMethod));
          }
        }
        if (false && subTypeSet.size() > 500)
          new RuntimeException(
                  receiverClassName
                      + " has "
                      + subTypeSet.size()
                      + " subclasses, "
                      + result.size()
                      + " of which implement "
                      + methodName
                      + methodSig
                      + " "
                      + invokeInstruction)
              .printStackTrace(System.out);
      }
    }
    return result;
  }

  /**
   * Return whether or not the given method is concrete.
   *
   * @param xmethod the method
   * @return true if the method is concrete, false otherwise
   */
  @Deprecated
  public static boolean isConcrete(XMethod xmethod) {
    int accessFlags = xmethod.getAccessFlags();
    return (accessFlags & Constants.ACC_ABSTRACT) == 0 && (accessFlags & Constants.ACC_NATIVE) == 0;
  }

  /**
   * Find a field with given name defined in given class.
   *
   * @param className the name of the class
   * @param fieldName the name of the field
   * @return the Field, or null if no such field could be found
   */
  public static Field findField(String className, String fieldName) throws ClassNotFoundException {
    JavaClass jclass = Repository.lookupClass(className);

    while (jclass != null) {
      Field[] fieldList = jclass.getFields();
      for (Field field : fieldList) {
        if (field.getName().equals(fieldName)) {
          return field;
        }
      }

      jclass = jclass.getSuperClass();
    }

    return null;
  }

  /**
   * Look up a field with given name and signature in given class, returning it as an {@link XField
   * XField} object. If a field can't be found in the immediate class, its superclass is search, and
   * so forth.
   *
   * @param className name of the class through which the field is referenced
   * @param fieldName name of the field
   * @param fieldSig signature of the field
   * @param isStatic true if field is static, false otherwise
   * @return an XField object representing the field, or null if no such field could be found
   */
  public static XField findXField(
      String className, String fieldName, String fieldSig, boolean isStatic)
      throws ClassNotFoundException {

    return XFactory.createXField(className, fieldName, fieldSig, isStatic);
  }

  /**
   * Look up the field referenced by given FieldInstruction, returning it as an {@link XField
   * XField} object.
   *
   * @param fins the FieldInstruction
   * @param cpg the ConstantPoolGen used by the class containing the instruction
   * @return an XField object representing the field, or null if no such field could be found
   */
  public static XField findXField(FieldInstruction fins, @NonNull ConstantPoolGen cpg)
      throws ClassNotFoundException {

    String className = fins.getClassName(cpg);
    String fieldName = fins.getFieldName(cpg);
    String fieldSig = fins.getSignature(cpg);

    boolean isStatic =
        (fins.getOpcode() == Constants.GETSTATIC || fins.getOpcode() == Constants.PUTSTATIC);

    XField xfield = findXField(className, fieldName, fieldSig, isStatic);
    short opcode = fins.getOpcode();
    if (xfield != null
        && xfield.isResolved()
        && xfield.isStatic() == (opcode == Constants.GETSTATIC || opcode == Constants.PUTSTATIC))
      return xfield;
    else return null;
  }

  /**
   * Determine whether the given INVOKESTATIC instruction is an inner-class field accessor method.
   *
   * @param inv the INVOKESTATIC instruction
   * @param cpg the ConstantPoolGen for the method
   * @return true if the instruction is an inner-class field accessor, false if not
   */
  public static boolean isInnerClassAccess(INVOKESTATIC inv, ConstantPoolGen cpg) {
    String methodName = inv.getName(cpg);
    return methodName.startsWith("access$");
  }

  /**
   * Get the InnerClassAccess for access method called by given INVOKESTATIC.
   *
   * @param inv the INVOKESTATIC instruction
   * @param cpg the ConstantPoolGen for the method
   * @return the InnerClassAccess, or null if the instruction is not an inner-class access
   */
  public static InnerClassAccess getInnerClassAccess(INVOKESTATIC inv, ConstantPoolGen cpg)
      throws ClassNotFoundException {

    String className = inv.getClassName(cpg);
    String methodName = inv.getName(cpg);
    String methodSig = inv.getSignature(cpg);

    InnerClassAccess access =
        AnalysisContext.currentAnalysisContext()
            .getInnerClassAccessMap()
            .getInnerClassAccess(className, methodName);
    return (access != null && access.getMethodSignature().equals(methodSig)) ? access : null;
  }
}
public class FindFinalizeInvocations extends BytecodeScanningDetector implements StatelessDetector {
  private static final boolean DEBUG = SystemProperties.getBoolean("ffi.debug");

  private BugReporter bugReporter;
  private final BugAccumulator bugAccumulator;

  public FindFinalizeInvocations(BugReporter bugReporter) {
    this.bugReporter = bugReporter;
    this.bugAccumulator = new BugAccumulator(bugReporter);
  }

  boolean sawSuperFinalize;

  @Override
  public void visit(Method obj) {
    if (DEBUG) System.out.println("FFI: visiting " + getFullyQualifiedMethodName());
    if (getMethodName().equals("finalize")
        && getMethodSig().equals("()V")
        && (obj.getAccessFlags() & (ACC_PUBLIC)) != 0)
      bugReporter.reportBug(
          new BugInstance(this, "FI_PUBLIC_SHOULD_BE_PROTECTED", NORMAL_PRIORITY)
              .addClassAndMethod(this));
  }

  @Override
  public void visit(Code obj) {
    sawSuperFinalize = false;
    super.visit(obj);
    bugAccumulator.reportAccumulatedBugs();
    if (!getMethodName().equals("finalize") || !getMethodSig().equals("()V")) return;
    String overridesFinalizeIn =
        Lookup.findSuperImplementor(getDottedClassName(), "finalize", "()V", bugReporter);
    boolean superHasNoFinalizer = overridesFinalizeIn.equals("java.lang.Object");
    // System.out.println("superclass: " + superclassName);
    if (obj.getCode().length == 1) {
      if (superHasNoFinalizer) {
        if (!getMethod().isFinal())
          bugReporter.reportBug(
              new BugInstance(this, "FI_EMPTY", NORMAL_PRIORITY).addClassAndMethod(this));
      } else
        bugReporter.reportBug(
            new BugInstance(this, "FI_NULLIFY_SUPER", NORMAL_PRIORITY)
                .addClassAndMethod(this)
                .addClass(overridesFinalizeIn));
    } else if (obj.getCode().length == 5 && sawSuperFinalize)
      bugReporter.reportBug(
          new BugInstance(this, "FI_USELESS", NORMAL_PRIORITY).addClassAndMethod(this));
    else if (!sawSuperFinalize && !superHasNoFinalizer)
      bugReporter.reportBug(
          new BugInstance(this, "FI_MISSING_SUPER_CALL", NORMAL_PRIORITY)
              .addClassAndMethod(this)
              .addClass(overridesFinalizeIn));
  }

  @Override
  public void sawOpcode(int seen) {
    if (seen == INVOKEVIRTUAL
        && getNameConstantOperand().equals("finalize")
        && getSigConstantOperand().equals("()V")) {
      bugAccumulator.accumulateBug(
          new BugInstance(
                  this,
                  "FI_EXPLICIT_INVOCATION",
                  getMethodName().equals("finalize") && getMethodSig().equals("()V")
                      ? HIGH_PRIORITY
                      : NORMAL_PRIORITY)
              .addClassAndMethod(this)
              .addCalledMethod(this),
          this);
    }
    if (seen == INVOKESPECIAL && getNameConstantOperand().equals("finalize"))
      sawSuperFinalize = true;
  }
}
public class SerializableIdiom extends OpcodeStackDetector {

  private static final boolean DEBUG = SystemProperties.getBoolean("se.debug");

  static final boolean reportTransientFieldOfNonSerializableClass =
      SystemProperties.getBoolean("reportTransientFieldOfNonSerializableClass");

  boolean sawSerialVersionUID;
  boolean isSerializable, implementsSerializableDirectly;
  boolean isExternalizable;
  boolean isGUIClass;
  boolean isEjbImplClass;
  boolean foundSynthetic;
  boolean seenTransientField;
  boolean foundSynchronizedMethods;
  boolean writeObjectIsSynchronized;
  private BugReporter bugReporter;
  boolean isAbstract;
  private List<BugInstance> fieldWarningList = new LinkedList<BugInstance>();
  private HashMap<String, XField> fieldsThatMightBeAProblem = new HashMap<String, XField>();
  private HashMap<XField, Integer> transientFieldsUpdates = new HashMap<XField, Integer>();
  private HashSet<XField> transientFieldsSetInConstructor = new HashSet<XField>();
  private HashSet<XField> transientFieldsSetToDefaultValueInConstructor = new HashSet<XField>();

  private boolean sawReadExternal;
  private boolean sawWriteExternal;
  private boolean sawReadObject;
  private boolean sawReadResolve;
  private boolean sawWriteObject;
  private boolean superClassImplementsSerializable;
  private boolean superClassHasReadObject;
  private boolean hasPublicVoidConstructor;
  private boolean superClassHasVoidConstructor;
  private boolean directlyImplementsExternalizable;
  // private JavaClass serializable;
  // private JavaClass collection;
  // private JavaClass map;
  // private boolean isRemote;

  public SerializableIdiom(BugReporter bugReporter) {
    this.bugReporter = bugReporter;
  }

  @Override
  public void visitClassContext(ClassContext classContext) {
    classContext.getJavaClass().accept(this);
    flush();
  }

  private void flush() {
    if (!isAbstract
        && !((sawReadExternal && sawWriteExternal) || (sawReadObject && sawWriteObject))) {
      for (BugInstance aFieldWarningList : fieldWarningList)
        bugReporter.reportBug(aFieldWarningList);
    }
    fieldWarningList.clear();
  }

  static final Pattern anonymousInnerClassNamePattern = Pattern.compile(".+\\$\\d+");
  boolean isAnonymousInnerClass;
  boolean innerClassHasOuterInstance;
  private boolean isEnum;

  @Override
  public void visit(JavaClass obj) {
    String superClassname = obj.getSuperclassName();
    // System.out.println("superclass of " + getClassName() + " is " + superClassname);
    isEnum = superClassname.equals("java.lang.Enum");
    if (isEnum) return;
    int flags = obj.getAccessFlags();
    isAbstract = (flags & ACC_ABSTRACT) != 0 || (flags & ACC_INTERFACE) != 0;
    isAnonymousInnerClass = anonymousInnerClassNamePattern.matcher(getClassName()).matches();
    innerClassHasOuterInstance = false;
    for (Field f : obj.getFields()) {
      if (f.getName().equals("this$0")) {
        innerClassHasOuterInstance = true;
        break;
      }
    }

    sawSerialVersionUID = false;
    isSerializable = implementsSerializableDirectly = false;
    isExternalizable = false;
    directlyImplementsExternalizable = false;
    isGUIClass = false;
    isEjbImplClass = false;
    seenTransientField = false;
    // boolean isEnum = obj.getSuperclassName().equals("java.lang.Enum");
    fieldsThatMightBeAProblem.clear();
    transientFieldsUpdates.clear();
    transientFieldsSetInConstructor.clear();
    transientFieldsSetToDefaultValueInConstructor.clear();
    // isRemote = false;

    // Does this class directly implement Serializable?
    String[] interface_names = obj.getInterfaceNames();
    for (String interface_name : interface_names) {
      if (interface_name.equals("java.io.Externalizable")) {
        directlyImplementsExternalizable = true;
        isExternalizable = true;
        if (DEBUG) {
          System.out.println("Directly implements Externalizable: " + getClassName());
        }
      } else if (interface_name.equals("java.io.Serializable")) {
        implementsSerializableDirectly = true;
        isSerializable = true;
        if (DEBUG) {
          System.out.println("Directly implements Serializable: " + getClassName());
        }
        break;
      }
    }

    // Does this class indirectly implement Serializable?
    if (!isSerializable) {
      if (Subtypes2.instanceOf(obj, "java.io.Externalizable")) {
        isExternalizable = true;
        if (DEBUG) {
          System.out.println("Indirectly implements Externalizable: " + getClassName());
        }
      }
      if (Subtypes2.instanceOf(obj, "java.io.Serializable")) {
        isSerializable = true;
        if (DEBUG) {
          System.out.println("Indirectly implements Serializable: " + getClassName());
        }
      }
    }

    hasPublicVoidConstructor = false;
    superClassHasVoidConstructor = true;
    superClassHasReadObject = false;
    superClassImplementsSerializable = isSerializable && !implementsSerializableDirectly;
    ClassDescriptor superclassDescriptor = getXClass().getSuperclassDescriptor();
    if (superclassDescriptor != null)
      try {
        XClass superXClass =
            Global.getAnalysisCache().getClassAnalysis(XClass.class, superclassDescriptor);
        if (superXClass != null) {
          superClassImplementsSerializable =
              AnalysisContext.currentAnalysisContext()
                  .getSubtypes2()
                  .isSubtype(
                      superXClass.getClassDescriptor(),
                      DescriptorFactory.createClassDescriptor(java.io.Serializable.class));
          superClassHasVoidConstructor = false;
          for (XMethod m : superXClass.getXMethods()) {
            if (m.getName().equals("<init>") && m.getSignature().equals("()V") && !m.isPrivate()) {
              superClassHasVoidConstructor = true;
            }
            if (m.getName().equals("readObject")
                && m.getSignature().equals("(Ljava/io/ObjectInputStream;)V")
                && m.isPrivate()) superClassHasReadObject = true;
          }
        }
      } catch (ClassNotFoundException e) {
        bugReporter.reportMissingClass(e);
      } catch (CheckedAnalysisException e) {
        bugReporter.logError("huh", e);
      }

    // Is this a GUI  or other class that is rarely serialized?

    isGUIClass = false;
    isEjbImplClass = false;
    if (true || !directlyImplementsExternalizable && !implementsSerializableDirectly) {
      isEjbImplClass = Subtypes2.instanceOf(obj, "javax.ejb.SessionBean");
      isGUIClass =
          (Subtypes2.instanceOf(obj, "java.lang.Throwable")
              || Subtypes2.instanceOf(obj, "java.awt.Component")
              || Subtypes2.instanceOf(obj, "java.awt.Component$AccessibleAWTComponent")
              || Subtypes2.instanceOf(obj, "java.awt.event.ActionListener")
              || Subtypes2.instanceOf(obj, "java.util.EventListener"));
      if (!isGUIClass) {
        JavaClass o = obj;
        while (o != null) {
          if (o.getClassName().startsWith("java.awt")
              || o.getClassName().startsWith("javax.swing")) {
            isGUIClass = true;
            break;
          }
          try {
            o = o.getSuperClass();
          } catch (ClassNotFoundException e) {
            break;
          }
        }
      }
    }

    foundSynthetic = false;
    foundSynchronizedMethods = false;
    writeObjectIsSynchronized = false;

    sawReadExternal = sawWriteExternal = sawReadObject = sawReadResolve = sawWriteObject = false;
    if (isSerializable) {
      for (Method m : obj.getMethods()) {

        if (m.getName().equals("readObject")
            && m.getSignature().equals("(Ljava/io/ObjectInputStream;)V")) sawReadObject = true;
        else if (m.getName().equals("readResolve") && m.getSignature().startsWith("()"))
          sawReadResolve = true;
        else if (m.getName().equals("readObjectNoData") && m.getSignature().equals("()V"))
          sawReadObject = true;
        else if (m.getName().equals("writeObject")
            && m.getSignature().equals("(Ljava/io/ObjectOutputStream;)V")) sawWriteObject = true;
      }
      for (Field f : obj.getFields()) {
        if (f.isTransient()) seenTransientField = true;
      }
    }
  }

  private boolean strongEvidenceForIntendedSerialization() {
    return implementsSerializableDirectly
        || sawReadObject
        || sawReadResolve
        || sawWriteObject
        || seenTransientField;
  }

  @Override
  public void visitAfter(JavaClass obj) {
    if (isEnum) return;
    if (DEBUG) {
      System.out.println(getDottedClassName());
      System.out.println("  hasPublicVoidConstructor: " + hasPublicVoidConstructor);
      System.out.println("  superClassHasVoidConstructor: " + superClassHasVoidConstructor);
      System.out.println("  isExternalizable: " + isExternalizable);
      System.out.println("  isSerializable: " + isSerializable);
      System.out.println("  isAbstract: " + isAbstract);
      System.out.println("  superClassImplementsSerializable: " + superClassImplementsSerializable);
      System.out.println("  isGUIClass: " + isGUIClass);
      System.out.println("  isEjbImplClass: " + isEjbImplClass);
    }
    if (isSerializable
        && !sawReadObject
        && !sawReadResolve
        && seenTransientField
        && !superClassHasReadObject) {
      for (Map.Entry<XField, Integer> e : transientFieldsUpdates.entrySet()) {

        XField fieldX = e.getKey();
        int priority = NORMAL_PRIORITY;
        if (transientFieldsSetInConstructor.contains(e.getKey())) priority--;

        if (isGUIClass) priority++;
        if (isEjbImplClass) priority++;
        if (e.getValue() < 3) priority++;
        if (transientFieldsSetToDefaultValueInConstructor.contains(e.getKey())) priority++;
        if (obj.isAbstract()) {
          priority++;
          if (priority < Priorities.LOW_PRIORITY) priority = Priorities.LOW_PRIORITY;
        }

        try {
          double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(fieldX.getSignature());
          if (isSerializable < 0.6) priority++;
        } catch (ClassNotFoundException e1) {
          // ignore it
        }

        bugReporter.reportBug(
            new BugInstance(this, "SE_TRANSIENT_FIELD_NOT_RESTORED", priority)
                .addClass(getThisClass())
                .addField(fieldX));
      }
    }
    if (isSerializable
        && !isExternalizable
        && !superClassHasVoidConstructor
        && !superClassImplementsSerializable) {
      int priority =
          implementsSerializableDirectly || seenTransientField
              ? HIGH_PRIORITY
              : (sawSerialVersionUID ? NORMAL_PRIORITY : LOW_PRIORITY);
      if (isGUIClass) priority++;
      if (isEjbImplClass) priority++;
      bugReporter.reportBug(
          new BugInstance(this, "SE_NO_SUITABLE_CONSTRUCTOR", priority)
              .addClass(getThisClass().getClassName()));
    }
    // Downgrade class-level warnings if it's a GUI or EJB-implementation class.
    int priority = (isGUIClass || isEjbImplClass) ? LOW_PRIORITY : NORMAL_PRIORITY;
    if (obj.getClassName().endsWith("_Stub")) priority++;

    if (isExternalizable && !hasPublicVoidConstructor && !isAbstract)
      bugReporter.reportBug(
          new BugInstance(
                  this,
                  "SE_NO_SUITABLE_CONSTRUCTOR_FOR_EXTERNALIZATION",
                  directlyImplementsExternalizable ? HIGH_PRIORITY : NORMAL_PRIORITY)
              .addClass(getThisClass().getClassName()));
    if (!foundSynthetic) priority++;
    if (seenTransientField) priority--;
    if (!isAnonymousInnerClass
        && !isExternalizable
        && !isGUIClass
        && !obj.isAbstract()
        && isSerializable
        && !isAbstract
        && !sawSerialVersionUID
        && !isEjbImplClass)
      bugReporter.reportBug(
          new BugInstance(this, "SE_NO_SERIALVERSIONID", priority).addClass(this));

    if (writeObjectIsSynchronized && !foundSynchronizedMethods)
      bugReporter.reportBug(
          new BugInstance(this, "WS_WRITEOBJECT_SYNC", LOW_PRIORITY).addClass(this));
  }

  @Override
  public void visit(Method obj) {

    int accessFlags = obj.getAccessFlags();
    boolean isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0;
    if (getMethodName().equals("<init>")
        && getMethodSig().equals("()V")
        && (accessFlags & ACC_PUBLIC) != 0) hasPublicVoidConstructor = true;
    if (!getMethodName().equals("<init>") && isSynthetic(obj)) foundSynthetic = true;
    // System.out.println(methodName + isSynchronized);

    if (getMethodName().equals("readExternal")
        && getMethodSig().equals("(Ljava/io/ObjectInput;)V")) {
      sawReadExternal = true;
      if (DEBUG && !obj.isPrivate())
        System.out.println("Non-private readExternal method in: " + getDottedClassName());
    } else if (getMethodName().equals("writeExternal")
        && getMethodSig().equals("(Ljava/io/Objectoutput;)V")) {
      sawWriteExternal = true;
      if (DEBUG && !obj.isPrivate())
        System.out.println("Non-private writeExternal method in: " + getDottedClassName());
    } else if (getMethodName().equals("readResolve")
        && getMethodSig().startsWith("()")
        && isSerializable) {
      sawReadResolve = true;
      if (!getMethodSig().equals("()Ljava/lang/Object;"))
        bugReporter.reportBug(
            new BugInstance(this, "SE_READ_RESOLVE_MUST_RETURN_OBJECT", HIGH_PRIORITY)
                .addClassAndMethod(this));
      else if (obj.isStatic())
        bugReporter.reportBug(
            new BugInstance(this, "SE_READ_RESOLVE_IS_STATIC", HIGH_PRIORITY)
                .addClassAndMethod(this));
      else if (obj.isPrivate())
        try {
          Set<ClassDescriptor> subtypes =
              AnalysisContext.currentAnalysisContext()
                  .getSubtypes2()
                  .getSubtypes(getClassDescriptor());
          if (subtypes.size() > 1) {
            BugInstance bug =
                new BugInstance(this, "SE_PRIVATE_READ_RESOLVE_NOT_INHERITED", NORMAL_PRIORITY)
                    .addClassAndMethod(this);
            boolean nasty = false;
            for (ClassDescriptor subclass : subtypes)
              if (!subclass.equals(getClassDescriptor())) {

                XClass xSub = AnalysisContext.currentXFactory().getXClass(subclass);
                if (xSub != null
                    && xSub.findMethod("readResolve", "()Ljava/lang/Object;", false) == null
                    && xSub.findMethod("writeReplace", "()Ljava/lang/Object;", false) == null) {
                  bug.addClass(subclass).describe(ClassAnnotation.SUBCLASS_ROLE);
                  nasty = true;
                }
              }
            if (nasty) bug.setPriority(HIGH_PRIORITY);
            else if (!getThisClass().isPublic()) bug.setPriority(LOW_PRIORITY);
            bugReporter.reportBug(bug);
          }

        } catch (ClassNotFoundException e) {
          bugReporter.reportMissingClass(e);
        }

    } else if (getMethodName().equals("readObject")
        && getMethodSig().equals("(Ljava/io/ObjectInputStream;)V")
        && isSerializable) {
      sawReadObject = true;
      if (!obj.isPrivate())
        bugReporter.reportBug(
            new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY)
                .addClassAndMethod(this));

    } else if (getMethodName().equals("readObjectNoData")
        && getMethodSig().equals("()V")
        && isSerializable) {

      if (!obj.isPrivate())
        bugReporter.reportBug(
            new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY)
                .addClassAndMethod(this));

    } else if (getMethodName().equals("writeObject")
        && getMethodSig().equals("(Ljava/io/ObjectOutputStream;)V")
        && isSerializable) {
      sawWriteObject = true;
      if (!obj.isPrivate())
        bugReporter.reportBug(
            new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY)
                .addClassAndMethod(this));
    }

    if (isSynchronized) {
      if (getMethodName().equals("readObject")
          && getMethodSig().equals("(Ljava/io/ObjectInputStream;)V")
          && isSerializable)
        bugReporter.reportBug(
            new BugInstance(this, "RS_READOBJECT_SYNC", NORMAL_PRIORITY).addClass(this));
      else if (getMethodName().equals("writeObject")
          && getMethodSig().equals("(Ljava/io/ObjectOutputStream;)V")
          && isSerializable) writeObjectIsSynchronized = true;
      else foundSynchronizedMethods = true;
    }
    super.visit(obj);
  }

  boolean isSynthetic(FieldOrMethod obj) {
    Attribute[] a = obj.getAttributes();
    for (Attribute aA : a) if (aA instanceof Synthetic) return true;
    return false;
  }

  @Override
  public void visit(Code obj) {
    if (isSerializable) {
      super.visit(obj);
    }
  }

  @Override
  public void sawOpcode(int seen) {
    if (seen == PUTFIELD) {
      XField xField = getXFieldOperand();
      if (xField != null && xField.getClassDescriptor().equals(getClassDescriptor())) {
        Item first = stack.getStackItem(0);
        boolean isPutOfDefaultValue = first.isNull(); // huh?? || first.isInitialParameter();
        if (!isPutOfDefaultValue && first.getConstant() != null) {
          Object constant = first.getConstant();
          if (constant instanceof Number && ((Number) constant).intValue() == 0
              || constant.equals(Boolean.FALSE)) isPutOfDefaultValue = true;
        }

        if (isPutOfDefaultValue) {
          if (getMethodName().equals("<init>"))
            transientFieldsSetToDefaultValueInConstructor.add(xField);
        } else {
          String nameOfField = getNameConstantOperand();

          if (transientFieldsUpdates.containsKey(xField)) {
            if (getMethodName().equals("<init>")) transientFieldsSetInConstructor.add(xField);
            else transientFieldsUpdates.put(xField, transientFieldsUpdates.get(xField) + 1);
          } else if (fieldsThatMightBeAProblem.containsKey(nameOfField)) {
            try {

              JavaClass classStored = first.getJavaClass();
              if (classStored == null) {
                return;
              }
              double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(classStored);
              if (isSerializable <= 0.2) {
                XField f = fieldsThatMightBeAProblem.get(nameOfField);

                String sig = f.getSignature();
                // System.out.println("Field signature: " + sig);
                // System.out.println("Class stored: " +
                // classStored.getClassName());
                String genSig = "L" + classStored.getClassName().replace('.', '/') + ";";
                if (!sig.equals(genSig)) {
                  double bias = 0.0;
                  if (!getMethodName().equals("<init>")) bias = 1.0;
                  int priority = computePriority(isSerializable, bias);

                  fieldWarningList.add(
                      new BugInstance(this, "SE_BAD_FIELD_STORE", priority)
                          .addClass(getThisClass().getClassName())
                          .addField(f)
                          .addType(genSig)
                          .describe("TYPE_FOUND")
                          .addSourceLine(this));
                }
              }
            } catch (Exception e) {
              // ignore it
            }
          }
        }
      }
    }
  }

  @Override
  public void visit(Field obj) {
    int flags = obj.getAccessFlags();
    if (isEjbImplClass) {
      ClassDescriptor fieldType =
          DescriptorFactory.createClassDescriptorFromFieldSignature(obj.getSignature());
      if (fieldType != null) {
        if (Subtypes2.instanceOf(fieldType, "javax.ejb.SessionContext")
            || Subtypes2.instanceOf(fieldType, "javax.transaction.UserTransaction")
            || Subtypes2.instanceOf(fieldType, "javax.ejb.EJBHome")
            || Subtypes2.instanceOf(fieldType, "javax.ejb.EJBObject")
            || Subtypes2.instanceOf(fieldType, "javax.naming.Context")) {
          if (obj.isTransient())
            bugReporter.reportBug(
                new BugInstance(this, "UNKNOWN", NORMAL_PRIORITY)
                    .addClass(this)
                    .addVisitedField(this));
          return;
        }
      }
    }

    if (obj.isTransient()) {
      if (isSerializable && !isExternalizable) {
        seenTransientField = true;
        transientFieldsUpdates.put(getXField(), 0);
      } else if (reportTransientFieldOfNonSerializableClass) {
        bugReporter.reportBug(
            new BugInstance(this, "SE_TRANSIENT_FIELD_OF_NONSERIALIZABLE_CLASS", NORMAL_PRIORITY)
                .addClass(this)
                .addVisitedField(this));
      }
    } else if (getClassName().indexOf("ObjectStreamClass") == -1
        && isSerializable
        && !isExternalizable
        && getFieldSig().indexOf("L") >= 0
        && !obj.isTransient()
        && !obj.isStatic()) {
      if (DEBUG) {
        System.out.println(
            "Examining non-transient field with name: "
                + getFieldName()
                + ", sig: "
                + getFieldSig());
      }
      try {

        double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(getFieldSig());
        if (DEBUG) {
          System.out.println("  isSerializable: " + isSerializable);
        }
        if (isSerializable < 1.0)
          fieldsThatMightBeAProblem.put(obj.getName(), XFactory.createXField(this));
        if (isSerializable < 0.9) {

          // Priority is LOW for GUI classes (unless explicitly marked Serializable),
          // HIGH if the class directly implements Serializable,
          // NORMAL otherwise.
          int priority = computePriority(isSerializable, 0);
          if (!strongEvidenceForIntendedSerialization()) {
            if (obj.getName().startsWith("this$")) priority = Math.max(priority, NORMAL_PRIORITY);
            else if (innerClassHasOuterInstance) {
              if (isAnonymousInnerClass) priority += 2;
              else priority += 1;
            }
            if (isGUIClass || isEjbImplClass) priority++;
          } else if (isGUIClass || isEjbImplClass) priority = Math.max(priority, NORMAL_PRIORITY);
          if (DEBUG)
            System.out.println(
                "SE_BAD_FIELD: "
                    + getThisClass().getClassName()
                    + " "
                    + obj.getName()
                    + " "
                    + isSerializable
                    + " "
                    + implementsSerializableDirectly
                    + " "
                    + sawSerialVersionUID
                    + " "
                    + isGUIClass
                    + " "
                    + isEjbImplClass);
          // Report is queued until after the entire class has been seen.

          if (obj.getName().equals("this$0"))
            fieldWarningList.add(
                new BugInstance(this, "SE_BAD_FIELD_INNER_CLASS", priority)
                    .addClass(getThisClass().getClassName()));
          else if (isSerializable < 0.9)
            fieldWarningList.add(
                new BugInstance(this, "SE_BAD_FIELD", priority)
                    .addClass(getThisClass().getClassName())
                    .addField(getDottedClassName(), obj.getName(), getFieldSig(), false));
        } else if (!isGUIClass && !isEjbImplClass && obj.getName().equals("this$0"))
          fieldWarningList.add(
              new BugInstance(
                      this,
                      "SE_INNER_CLASS",
                      implementsSerializableDirectly ? NORMAL_PRIORITY : LOW_PRIORITY)
                  .addClass(getThisClass().getClassName()));
      } catch (ClassNotFoundException e) {
        if (DEBUG) {
          System.out.println("Caught ClassNotFoundException");
        }
        bugReporter.reportMissingClass(e);
      }
    }

    if (!getFieldName().startsWith("this") && isSynthetic(obj)) foundSynthetic = true;
    if (!getFieldName().equals("serialVersionUID")) return;
    int mask = ACC_STATIC | ACC_FINAL;
    if (!getFieldSig().equals("I") && !getFieldSig().equals("J")) return;
    if ((flags & mask) == mask && getFieldSig().equals("I")) {
      bugReporter.reportBug(
          new BugInstance(this, "SE_NONLONG_SERIALVERSIONID", LOW_PRIORITY)
              .addClass(this)
              .addVisitedField(this));
      sawSerialVersionUID = true;
      return;
    } else if ((flags & ACC_STATIC) == 0) {
      bugReporter.reportBug(
          new BugInstance(this, "SE_NONSTATIC_SERIALVERSIONID", NORMAL_PRIORITY)
              .addClass(this)
              .addVisitedField(this));
      return;
    } else if ((flags & ACC_FINAL) == 0) {
      bugReporter.reportBug(
          new BugInstance(this, "SE_NONFINAL_SERIALVERSIONID", NORMAL_PRIORITY)
              .addClass(this)
              .addVisitedField(this));
      return;
    }
    sawSerialVersionUID = true;
  }

  private int computePriority(double isSerializable, double bias) {
    int priority = (int) (1.9 + isSerializable * 3 + bias);

    if (strongEvidenceForIntendedSerialization()) priority--;
    else if (sawSerialVersionUID && priority > NORMAL_PRIORITY) priority--;
    else priority = Math.max(priority, NORMAL_PRIORITY);
    return priority;
  }
}
/**
 * Dataflow analysis to find values unconditionally dereferenced in the future.
 *
 * @author David Hovemeyer
 */
public class UnconditionalValueDerefAnalysis
    extends BackwardDataflowAnalysis<UnconditionalValueDerefSet> {

  public static final boolean DEBUG = SystemProperties.getBoolean("fnd.derefs.debug");

  public static final boolean ASSUME_NONZERO_TRIP_LOOPS =
      SystemProperties.getBoolean("fnd.derefs.nonzerotrip");

  public static final boolean IGNORE_DEREF_OF_NCP =
      SystemProperties.getBoolean("fnd.derefs.ignoreNCP", false);

  public static final boolean CHECK_ANNOTATIONS =
      SystemProperties.getBoolean("fnd.derefs.checkannotations", true);

  public static final boolean CHECK_CALLS =
      SystemProperties.getBoolean("fnd.derefs.checkcalls", true);

  public static final boolean DEBUG_CHECK_CALLS =
      SystemProperties.getBoolean("fnd.derefs.checkcalls.debug");

  private static final int NULLCHECK1[] = {Opcodes.DUP, Opcodes.INVOKESPECIAL, Opcodes.ATHROW};

  private static final int NULLCHECK2[] = {
    Opcodes.DUP, Opcodes.LDC, Opcodes.INVOKESPECIAL, Opcodes.ATHROW
  };

  private final CFG cfg;

  private final Method method;

  private final MethodGen methodGen;

  private final ValueNumberDataflow vnaDataflow;

  private final AssertionMethods assertionMethods;

  private IsNullValueDataflow invDataflow;

  private TypeDataflow typeDataflow;

  /**
   * Constructor.
   *
   * @param rdfs the reverse depth-first-search (for the block order)
   * @param cfg the CFG for the method
   * @param methodGen the MethodGen for the method
   * @param vnaDataflow
   * @param assertionMethods AssertionMethods for the analyzed class
   */
  public UnconditionalValueDerefAnalysis(
      ReverseDepthFirstSearch rdfs,
      DepthFirstSearch dfs,
      CFG cfg,
      Method method,
      MethodGen methodGen,
      ValueNumberDataflow vnaDataflow,
      AssertionMethods assertionMethods) {
    super(rdfs, dfs);
    this.cfg = cfg;
    this.methodGen = methodGen;
    this.method = method;
    this.vnaDataflow = vnaDataflow;
    this.assertionMethods = assertionMethods;
    if (DEBUG) {
      System.out.println(
          "UnconditionalValueDerefAnalysis analysis "
              + methodGen.getClassName()
              + "."
              + methodGen.getName()
              + " : "
              + methodGen.getSignature());
    }
  }

  @Override
  public String toString() {
    return this.getClass().getSimpleName() + " of " + method;
  }

  /**
   * HACK: use the given is-null dataflow to clear deref sets for values that are known to be
   * definitely non-null on a branch.
   *
   * @param invDataflow the IsNullValueDataflow to use
   */
  public void clearDerefsOnNonNullBranches(IsNullValueDataflow invDataflow) {
    this.invDataflow = invDataflow;
  }

  public void setTypeDataflow(TypeDataflow typeDataflow) {
    this.typeDataflow = typeDataflow;
  }

  @Override
  public boolean isFactValid(UnconditionalValueDerefSet fact) {
    return !fact.isTop() && !fact.isBottom();
  }

  private static boolean check(InstructionHandle h, int[] opcodes) {
    for (int opcode : opcodes) {
      if (h == null) {
        return false;
      }
      short opcode2 = h.getInstruction().getOpcode();
      if (opcode == Constants.LDC) {
        switch (opcode2) {
          case Constants.LDC:
          case Constants.ALOAD:
          case Constants.ALOAD_0:
          case Constants.ALOAD_1:
          case Constants.ALOAD_2:
          case Constants.ALOAD_3:
            break;
          default:
            return false;
        }
      } else if (opcode2 != opcode) {
        return false;
      }
      h = h.getNext();
    }
    return true;
  }

  public static boolean isNullCheck(InstructionHandle h, ConstantPoolGen cpg) {
    if (!(h.getInstruction() instanceof IFNONNULL)) {
      return false;
    }
    h = h.getNext();
    final Instruction newInstruction = h.getInstruction();
    if (!(newInstruction instanceof NEW)) {
      return false;
    }
    final ObjectType loadClassType = ((NEW) newInstruction).getLoadClassType(cpg);
    if (!"java.lang.NullPointerException".equals(loadClassType.getClassName())) {
      return false;
    }
    h = h.getNext();
    return check(h, NULLCHECK1) || check(h, NULLCHECK2);
  }

  private void handleNullCheck(
      Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact)
      throws DataflowAnalysisException {
    if (reportPotentialDereference(location, invDataflow.getFactAtLocation(location))) {
      ValueNumber vn = vnaFrame.getTopValue();
      fact.addDeref(vn, location);
    }
  }

  public static boolean reportPotentialDereference(Location location, IsNullValueFrame invFrame)
      throws DataflowAnalysisException {
    if (!invFrame.isValid()) {
      return false;
    }
    IsNullValue value = invFrame.getTopValue();
    if (value.isDefinitelyNotNull()) {
      return false;
    }
    if (value.isDefinitelyNull()) {
      return false;
    }
    return true;
  }

  @Override
  public void transferInstruction(
      InstructionHandle handle, BasicBlock basicBlock, UnconditionalValueDerefSet fact)
      throws DataflowAnalysisException {

    Instruction instruction = handle.getInstruction();
    if (fact.isTop()) {
      return;
    }
    Location location = new Location(handle, basicBlock);

    // If this is a call to an assertion method,
    // change the dataflow value to be TOP.
    // We don't want to report future derefs that would
    // be guaranteed only if the assertion methods
    // returns normally.
    // TODO: at some point, evaluate whether we should revisit this
    if (isAssertion(handle) // || handle.getInstruction() instanceof ATHROW
    ) {
      if (DEBUG) {
        System.out.println("MAKING BOTTOM0 AT: " + location);
      }
      fact.clear();
      return;
    }

    // Get value number frame
    ValueNumberFrame vnaFrame = vnaDataflow.getFactAtLocation(location);
    if (!vnaFrame.isValid()) {
      if (DEBUG) {
        System.out.println("MAKING TOP1 AT: " + location);
      }
      // Probably dead code.
      // Assume this location can't be reached.
      makeFactTop(fact);
      return;
    }
    if (isNullCheck(handle, methodGen.getConstantPool())) {
      handleNullCheck(location, vnaFrame, fact);
    }

    // Check for calls to a method that unconditionally dereferences
    // a parameter. Mark any such arguments as derefs.
    if (CHECK_CALLS && instruction instanceof InvokeInstruction) {
      checkUnconditionalDerefDatabase(location, vnaFrame, fact);
    }

    // If this is a method call instruction,
    // check to see if any of the parameters are @NonNull,
    // and treat them as dereferences.
    if (CHECK_ANNOTATIONS && instruction instanceof InvokeInstruction) {
      checkNonNullParams(location, vnaFrame, fact);
    }

    if (CHECK_ANNOTATIONS && instruction instanceof ARETURN) {
      XMethod thisMethod = XFactory.createXMethod(methodGen);
      checkNonNullReturnValue(thisMethod, location, vnaFrame, fact);
    }

    if (CHECK_ANNOTATIONS
        && (instruction instanceof PUTFIELD || instruction instanceof PUTSTATIC)) {
      checkNonNullPutField(location, vnaFrame, fact);
    }

    // Check to see if an instance value is dereferenced here
    checkInstance(location, vnaFrame, fact);

    /*
    if (false) {
        fact.cleanDerefSet(location, vnaFrame);
    }*/

    if (DEBUG && fact.isTop()) {
      System.out.println("MAKING TOP2 At: " + location);
    }
  }

  /**
   * Check method call at given location to see if it unconditionally dereferences a parameter. Mark
   * any such arguments as derefs.
   *
   * @param location the Location of the method call
   * @param vnaFrame ValueNumberFrame at the Location
   * @param fact the dataflow value to modify
   * @throws DataflowAnalysisException
   */
  private void checkUnconditionalDerefDatabase(
      Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact)
      throws DataflowAnalysisException {
    ConstantPoolGen constantPool = methodGen.getConstantPool();

    for (ValueNumber vn :
        checkUnconditionalDerefDatabase(
            location,
            vnaFrame,
            constantPool,
            invDataflow.getFactAtLocation(location),
            typeDataflow)) {
      fact.addDeref(vn, location);
    }
  }

  public static Set<ValueNumber> checkUnconditionalDerefDatabase(
      Location location,
      ValueNumberFrame vnaFrame,
      ConstantPoolGen constantPool,
      @CheckForNull IsNullValueFrame invFrame,
      TypeDataflow typeDataflow)
      throws DataflowAnalysisException {
    if (invFrame != null && !invFrame.isValid()) {
      return Collections.emptySet();
    }

    InvokeInstruction inv = (InvokeInstruction) location.getHandle().getInstruction();

    SignatureParser sigParser = new SignatureParser(inv.getSignature(constantPool));
    int numParams = sigParser.getNumParameters();
    if (numParams == 0 || !sigParser.hasReferenceParameters()) {
      return Collections.emptySet();
    }
    ParameterNullnessPropertyDatabase database =
        AnalysisContext.currentAnalysisContext().getUnconditionalDerefParamDatabase();
    if (database == null) {
      if (DEBUG_CHECK_CALLS) {
        System.out.println("no database!");
      }
      return Collections.emptySet();
    }

    TypeFrame typeFrame = typeDataflow.getFactAtLocation(location);
    if (!typeFrame.isValid()) {
      if (DEBUG_CHECK_CALLS) {
        System.out.println("invalid type frame!");
      }
      return Collections.emptySet();
    }

    try {
      Set<XMethod> targetSet = Hierarchy2.resolveMethodCallTargets(inv, typeFrame, constantPool);

      if (targetSet.isEmpty()) {
        return Collections.emptySet();
      }

      if (DEBUG_CHECK_CALLS) {
        System.out.println("target set size: " + targetSet.size());
      }
      // Compute the intersection of all properties
      ParameterProperty derefParamSet = null;
      for (XMethod target : targetSet) {
        if (target.isStub()) {
          continue;
        }
        if (DEBUG_CHECK_CALLS) {
          System.out.print("Checking: " + target + ": ");
        }

        ParameterProperty targetDerefParamSet = database.getProperty(target.getMethodDescriptor());
        if (targetDerefParamSet == null) {
          // Hmm...no information for this target.
          // assume it doesn't dereference anything
          if (DEBUG_CHECK_CALLS) {
            System.out.println("==> no information, assume no guaranteed dereferences");
          }
          return Collections.emptySet();
        }

        if (DEBUG_CHECK_CALLS) {
          System.out.println("==> " + targetDerefParamSet);
        }
        if (derefParamSet == null) {
          derefParamSet = new ParameterProperty();
          derefParamSet.copyFrom(targetDerefParamSet);
        } else {
          derefParamSet.intersectWith(targetDerefParamSet);
        }
      }

      if (derefParamSet == null || derefParamSet.isEmpty()) {
        if (DEBUG) {
          System.out.println("** Nothing");
        }
        return Collections.emptySet();
      }
      if (DEBUG_CHECK_CALLS) {
        System.out.println(
            "** Summary of call @ " + location.getHandle().getPosition() + ": " + derefParamSet);
      }

      HashSet<ValueNumber> requiredToBeNonnull = new HashSet<ValueNumber>();
      for (int i = 0; i < numParams; i++) {
        if (!derefParamSet.hasProperty(i)) {
          continue;
        }
        int argSlot = vnaFrame.getStackLocation(sigParser.getSlotsFromTopOfStackForParameter(i));
        if (invFrame != null && !reportDereference(invFrame, argSlot)) {
          continue;
        }
        if (DEBUG_CHECK_CALLS) {
          System.out.println(
              "  dereference @ " + location.getHandle().getPosition() + " of parameter " + i);
        }

        requiredToBeNonnull.add(vnaFrame.getValue(argSlot));
      }
      return requiredToBeNonnull;

    } catch (ClassNotFoundException e) {
      AnalysisContext.reportMissingClass(e);
    }
    return Collections.emptySet();
  }

  public static final boolean VERBOSE_NULLARG_DEBUG =
      SystemProperties.getBoolean("fnd.debug.nullarg.verbose");

  /**
   * If this is a method call instruction, check to see if any of the parameters are @NonNull, and
   * treat them as dereferences.
   *
   * @param location the Location of the instruction
   * @param vnaFrame the ValueNumberFrame at the Location of the instruction
   * @param fact the dataflow value to modify
   * @throws DataflowAnalysisException
   */
  private void checkNonNullReturnValue(
      XMethod thisMethod,
      Location location,
      ValueNumberFrame vnaFrame,
      UnconditionalValueDerefSet fact)
      throws DataflowAnalysisException {
    INullnessAnnotationDatabase database =
        AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase();

    if (database.getResolvedAnnotation(thisMethod, true) != NullnessAnnotation.NONNULL) {
      return;
    }
    if (reportPotentialDereference(location, invDataflow.getFactAtLocation(location))) {
      ValueNumber vn = vnaFrame.getTopValue();
      fact.addDeref(vn, location);
    }
  }

  /**
   * If this is a putfield or putstatic instruction, check to see if the field is @NonNull, and
   * treat it as dereferences.
   *
   * @param location the Location of the instruction
   * @param vnaFrame the ValueNumberFrame at the Location of the instruction
   * @param fact the dataflow value to modify
   * @throws DataflowAnalysisException
   */
  private void checkNonNullPutField(
      Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact)
      throws DataflowAnalysisException {
    INullnessAnnotationDatabase database =
        AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase();

    FieldInstruction fieldIns = (FieldInstruction) location.getHandle().getInstruction();

    XField field = XFactory.createXField(fieldIns, methodGen.getConstantPool());
    char firstChar = field.getSignature().charAt(0);
    if (firstChar != 'L' && firstChar != '[') {
      return;
    }
    NullnessAnnotation resolvedAnnotation = database.getResolvedAnnotation(field, true);
    if (resolvedAnnotation == NullnessAnnotation.NONNULL) {
      IsNullValueFrame invFrame = invDataflow.getFactAtLocation(location);
      if (!invFrame.isValid()) {
        return;
      }
      IsNullValue value = invFrame.getTopValue();
      if (reportDereference(value)) {
        ValueNumber vn = vnaFrame.getTopValue();
        fact.addDeref(vn, location);
      }
    }
  }

  /**
   * If this is a method call instruction, check to see if any of the parameters are @NonNull, and
   * treat them as dereferences.
   *
   * @param location the Location of the instruction
   * @param vnaFrame the ValueNumberFrame at the Location of the instruction
   * @param fact the dataflow value to modify
   * @throws DataflowAnalysisException
   */
  private void checkNonNullParams(
      Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact)
      throws DataflowAnalysisException {
    ConstantPoolGen constantPool = methodGen.getConstantPool();
    Set<ValueNumber> nonNullParams =
        checkNonNullParams(
            location, vnaFrame, constantPool, method, invDataflow.getFactAtLocation(location));
    for (ValueNumber vn : nonNullParams) {
      fact.addDeref(vn, location);
    }
  }

  public static Set<ValueNumber> checkAllNonNullParams(
      Location location,
      ValueNumberFrame vnaFrame,
      ConstantPoolGen constantPool,
      @CheckForNull Method method,
      @CheckForNull IsNullValueDataflow invDataflow,
      TypeDataflow typeDataflow)
      throws DataflowAnalysisException {
    IsNullValueFrame invFrame = null;
    if (invDataflow != null) {
      invFrame = invDataflow.getFactAtLocation(location);
    }
    Set<ValueNumber> result1 =
        checkNonNullParams(location, vnaFrame, constantPool, method, invFrame);
    Set<ValueNumber> result2 =
        checkUnconditionalDerefDatabase(location, vnaFrame, constantPool, invFrame, typeDataflow);
    if (result1.isEmpty()) {
      return result2;
    }
    if (result2.isEmpty()) {
      return result1;
    }
    result1.addAll(result2);
    return result1;
  }

  public static Set<ValueNumber> checkNonNullParams(
      Location location,
      ValueNumberFrame vnaFrame,
      ConstantPoolGen constantPool,
      @CheckForNull Method method,
      @CheckForNull IsNullValueFrame invFrame)
      throws DataflowAnalysisException {

    if (invFrame != null && !invFrame.isValid()) {
      return Collections.emptySet();
    }
    INullnessAnnotationDatabase database =
        AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase();

    InvokeInstruction inv = (InvokeInstruction) location.getHandle().getInstruction();
    XMethod called = XFactory.createXMethod(inv, constantPool);
    SignatureParser sigParser = new SignatureParser(called.getSignature());
    int numParams = sigParser.getNumParameters();

    Set<ValueNumber> result = new HashSet<ValueNumber>();
    Iterator<String> parameterIterator = sigParser.parameterSignatureIterator();
    for (int i = 0; i < numParams; i++) {
      String parameterSignature = parameterIterator.next();
      char firstChar = parameterSignature.charAt(0);
      if (firstChar != 'L' && firstChar != '[') {
        continue;
      }
      int offset = sigParser.getSlotsFromTopOfStackForParameter(i);
      if (invFrame != null) {
        int slot = invFrame.getStackLocation(offset);
        if (!reportDereference(invFrame, slot)) {
          continue;
        }
      }
      if (database.parameterMustBeNonNull(called, i)) {
        int catchSizeNPE =
            Util.getSizeOfSurroundingTryBlock(
                method, "java/lang/NullPointerException", location.getHandle().getPosition());
        int catchSizeNFE =
            Util.getSizeOfSurroundingTryBlock(
                method, "java/lang/NumberFormatException", location.getHandle().getPosition());
        if (catchSizeNPE == Integer.MAX_VALUE
            && (!"java.lang.Integer".equals(called.getClassName())
                || catchSizeNFE == Integer.MAX_VALUE)) {
          // Get the corresponding value number
          ValueNumber vn = vnaFrame.getArgument(inv, constantPool, i, sigParser);
          result.add(vn);
        }
      }
    }
    return result;
  }

  /**
   * Check to see if the instruction has a null check associated with it, and if so, add a
   * dereference.
   *
   * @param location the Location of the instruction
   * @param vnaFrame ValueNumberFrame at the Location of the instruction
   * @param fact the dataflow value to modify
   * @throws DataflowAnalysisException
   */
  private void checkInstance(
      Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact)
      throws DataflowAnalysisException {
    // See if this instruction has a null check.
    // If it does, the fall through predecessor will be
    // identify itself as the null check.
    if (!location.isFirstInstructionInBasicBlock()) {
      return;
    }
    if (invDataflow == null) {
      return;
    }
    BasicBlock fallThroughPredecessor =
        cfg.getPredecessorWithEdgeType(location.getBasicBlock(), EdgeTypes.FALL_THROUGH_EDGE);
    if (fallThroughPredecessor == null || !fallThroughPredecessor.isNullCheck()) {
      return;
    }

    // Get the null-checked value
    ValueNumber vn =
        vnaFrame.getInstance(location.getHandle().getInstruction(), methodGen.getConstantPool());

    // Ignore dereferences of this
    if (!methodGen.isStatic()) {
      ValueNumber v = vnaFrame.getValue(0);
      if (v.equals(vn)) {
        return;
      }
    }
    if (vn.hasFlag(ValueNumber.CONSTANT_CLASS_OBJECT)) {
      return;
    }

    IsNullValueFrame startFact = null;

    startFact = invDataflow.getStartFact(fallThroughPredecessor);

    if (!startFact.isValid()) {
      return;
    }

    int slot =
        startFact.getInstanceSlot(
            location.getHandle().getInstruction(), methodGen.getConstantPool());
    if (!reportDereference(startFact, slot)) {
      return;
    }
    if (DEBUG) {
      System.out.println("FOUND GUARANTEED DEREFERENCE");
      System.out.println("Load: " + vnaFrame.getLoad(vn));
      System.out.println("Pred: " + fallThroughPredecessor);
      System.out.println("startFact: " + startFact);
      System.out.println("Location: " + location);
      System.out.println("Value number frame: " + vnaFrame);
      System.out.println("Dereferenced valueNumber: " + vn);
      System.out.println("invDataflow: " + startFact);
      System.out.println("IGNORE_DEREF_OF_NCP: " + IGNORE_DEREF_OF_NCP);
    }
    // Mark the value number as being dereferenced at this location
    fact.addDeref(vn, location);
  }

  private static boolean reportDereference(IsNullValueFrame invFrameAtNullCheck, int instance) {
    return reportDereference(invFrameAtNullCheck.getValue(instance));
  }

  private static boolean reportDereference(IsNullValue value) {
    if (value.isDefinitelyNotNull()) {
      return false;
    }
    if (value.isDefinitelyNull()) {
      return false;
    }
    if (IGNORE_DEREF_OF_NCP && value.isNullOnComplicatedPath()) {
      return false;
    }
    return true;
  }

  /**
   * Return whether or not given instruction is an assertion.
   *
   * @param handle the instruction
   * @return true if instruction is an assertion, false otherwise
   */
  private boolean isAssertion(InstructionHandle handle) {
    return assertionMethods.isAssertionHandle(handle, methodGen.getConstantPool());
  }

  @Override
  public void copy(UnconditionalValueDerefSet source, UnconditionalValueDerefSet dest) {
    dest.makeSameAs(source);
  }

  @Override
  public UnconditionalValueDerefSet createFact() {
    return new UnconditionalValueDerefSet(vnaDataflow.getAnalysis().getNumValuesAllocated());
  }

  @Override
  public void initEntryFact(UnconditionalValueDerefSet result) throws DataflowAnalysisException {
    result.clear();
  }

  // /* (non-Javadoc)
  // * @see
  // edu.umd.cs.findbugs.ba.DataflowAnalysis#initResultFact(java.lang.Object)
  // */
  // public void initResultFact(UnconditionalValueDerefSet result) {
  // result.setIsTop();
  // }

  @Override
  public void makeFactTop(UnconditionalValueDerefSet fact) {
    fact.setIsTop();
  }

  @Override
  public boolean isTop(UnconditionalValueDerefSet fact) {
    return fact.isTop();
  }

  @Override
  public void meetInto(
      UnconditionalValueDerefSet fact, Edge edge, UnconditionalValueDerefSet result)
      throws DataflowAnalysisException {
    meetInto(fact, edge, result, false);
  }

  public void meetInto(
      UnconditionalValueDerefSet fact,
      Edge edge,
      UnconditionalValueDerefSet result,
      boolean onlyEdge) {
    if (isExceptionEdge(edge) && !onlyEdge) {
      if (DEBUG) {
        System.out.println("Skipping exception edge");
      }
      return;
    }

    ValueNumber knownNonnullOnBranch = null;
    // Edge transfer function
    if (isFactValid(fact)) {
      fact = propagateDerefSetsToMergeInputValues(fact, edge);
      if (invDataflow != null) {
        knownNonnullOnBranch = findValueKnownNonnullOnBranch(fact, edge);
        if (knownNonnullOnBranch != null) {
          fact = duplicateFact(fact);
          fact.clearDerefSet(knownNonnullOnBranch);
        }
      }
    }
    boolean isBackEdge = edge.isBackwardInBytecode();
    Set<Integer> loopExitBranches = ClassContext.getLoopExitBranches(method, methodGen);
    assert loopExitBranches != null;
    boolean sourceIsTopOfLoop = edge.sourceIsTopOfLoop(loopExitBranches);
    if (sourceIsTopOfLoop && edge.getType() == EdgeTypes.FALL_THROUGH_EDGE) {
      isBackEdge = true;
    }
    /*
    if (false && (edge.getType() == EdgeTypes.IFCMP_EDGE || sourceIsTopOfLoop)) {
        System.out.println("Meet into " + edge);
        System.out.println("  foo2: " + sourceIsTopOfLoop);
        System.out.println("  getType: " + edge.getType());
        System.out.println("  Backedge according to bytecode: " + isBackEdge);
        System.out.println("  Fact hashCode: " + System.identityHashCode(result));
        System.out.println("  Initial fact: " + result);
        System.out.println("  Edge fact: " + fact);
    }
     */
    if (result.isTop() || fact.isBottom()) {
      // Make result identical to other fact
      copy(fact, result);
      if (ASSUME_NONZERO_TRIP_LOOPS && isBackEdge && !fact.isTop()) {
        result.resultsFromBackEdge = true;
      }
    } else if (ASSUME_NONZERO_TRIP_LOOPS && isBackEdge && !fact.isTop()) {
      result.unionWith(fact, vnaDataflow.getAnalysis().getFactory());
      result.resultsFromBackEdge = true;
      if (DEBUG) {
        System.out.println(
            "\n Forcing union of " + System.identityHashCode(result) + " due to backedge info");
        System.out.println("  result: " + result);
      }

    } else if (result.isBottom() || fact.isTop()) {
      // No change in result fact
    } else {
      // Dataflow merge
      // (intersection of unconditional deref values)
      if (ASSUME_NONZERO_TRIP_LOOPS && result.resultsFromBackEdge) {
        result.backEdgeUpdateCount++;
        if (result.backEdgeUpdateCount < 10) {
          if (DEBUG) {
            System.out.println(
                "\n Union update of " + System.identityHashCode(result) + " due to backedge info");
          }
          result.unionWith(fact, vnaDataflow.getAnalysis().getFactory());
          return;
        }
      }
      result.mergeWith(fact, knownNonnullOnBranch, vnaDataflow.getAnalysis().getFactory());
      if (DEBUG) {
        System.out.println("  updated: " + System.identityHashCode(result));
        System.out.println("  result: " + result);
      }
    }
    if (DEBUG && isBackEdge && edge.getType() == EdgeTypes.IFCMP_EDGE) {
      System.out.println("  result: " + result);
    }
  }

  /**
   * Find out if any VNs in the source block contribute to unconditionally dereferenced VNs in the
   * target block. If so, the VN in the source block is also unconditionally dereferenced, and we
   * must propagate the target VN's dereferences.
   *
   * @param fact a dataflow value
   * @param edge edge to check for merge input values
   * @return possibly-modified dataflow value
   */
  private UnconditionalValueDerefSet propagateDerefSetsToMergeInputValues(
      UnconditionalValueDerefSet fact, Edge edge) {

    ValueNumberFrame blockValueNumberFrame = vnaDataflow.getResultFact(edge.getSource());
    ValueNumberFrame targetValueNumberFrame = vnaDataflow.getStartFact(edge.getTarget());

    UnconditionalValueDerefSet originalFact = fact;
    fact = duplicateFact(fact);

    if (blockValueNumberFrame.isValid() && targetValueNumberFrame.isValid()) {
      int slots = 0;
      if (targetValueNumberFrame.getNumSlots() == blockValueNumberFrame.getNumSlots()) {
        slots = targetValueNumberFrame.getNumSlots();
      } else if (targetValueNumberFrame.getNumLocals() == blockValueNumberFrame.getNumLocals()) {
        slots = targetValueNumberFrame.getNumLocals();
      }

      if (slots > 0) {
        if (DEBUG) {
          System.out.println("** Valid VNA frames for " + edge);
          System.out.println("** Block : " + blockValueNumberFrame);
          System.out.println("** Target: " + targetValueNumberFrame);
        }

        for (int i = 0; i < slots; i++) {
          ValueNumber blockVN = blockValueNumberFrame.getValue(i);
          ValueNumber targetVN = targetValueNumberFrame.getValue(i);
          if (blockVN.equals(targetVN)) {
            continue;
          }
          fact.clearDerefSet(blockVN);
          if (originalFact.isUnconditionallyDereferenced(targetVN)) {
            fact.setDerefSet(blockVN, originalFact.getUnconditionalDerefLocationSet(targetVN));
          }
        } // for all slots

        for (ValueNumber blockVN : blockValueNumberFrame.valueNumbersForLoads()) {
          AvailableLoad load = blockValueNumberFrame.getLoad(blockVN);
          if (load == null) {
            continue;
          }
          ValueNumber[] targetVNs = targetValueNumberFrame.getAvailableLoad(load);
          if (targetVNs != null) {
            for (ValueNumber targetVN : targetVNs) {
              if (targetVN.hasFlag(ValueNumber.PHI_NODE)
                  && fact.isUnconditionallyDereferenced(targetVN)
                  && !fact.isUnconditionallyDereferenced(blockVN)) {
                // Block VN is also dereferenced
                // unconditionally.
                AvailableLoad targetLoad = targetValueNumberFrame.getLoad(targetVN);
                if (!load.equals(targetLoad)) {
                  continue;
                }
                if (DEBUG) {
                  System.out.println(
                      "** Copy vn derefs for " + load + " from " + targetVN + " --> " + blockVN);
                  System.out.println(
                      "** block phi for "
                          + System.identityHashCode(blockValueNumberFrame)
                          + " is "
                          + blockValueNumberFrame.phiNodeForLoads);
                  System.out.println(
                      "** target phi for "
                          + System.identityHashCode(targetValueNumberFrame)
                          + " is "
                          + targetValueNumberFrame.phiNodeForLoads);
                }
                fact.setDerefSet(blockVN, fact.getUnconditionalDerefLocationSet(targetVN));
              }
            }
          }
        }
      }
    }
    if (DEBUG) {
      System.out.println("Target VNF: " + targetValueNumberFrame);
      System.out.println("Block VNF: " + blockValueNumberFrame);
      System.out.println("fact: " + fact);
    }
    fact.cleanDerefSet(null, blockValueNumberFrame);
    return fact;
  }

  /**
   * Return a duplicate of given dataflow fact.
   *
   * @param fact a dataflow fact
   * @return a duplicate of the input dataflow fact
   */
  private UnconditionalValueDerefSet duplicateFact(UnconditionalValueDerefSet fact) {
    UnconditionalValueDerefSet copyOfFact = createFact();
    copy(fact, copyOfFact);
    fact = copyOfFact;
    return fact;
  }

  /**
   * Clear deref sets of values if this edge is the non-null branch of an if comparison.
   *
   * @param fact a datflow fact
   * @param edge edge to check
   * @return possibly-modified dataflow fact
   */
  private @CheckForNull ValueNumber findValueKnownNonnullOnBranch(
      UnconditionalValueDerefSet fact, Edge edge) {

    IsNullValueFrame invFrame = invDataflow.getResultFact(edge.getSource());
    if (!invFrame.isValid()) {
      return null;
    }
    IsNullConditionDecision decision = invFrame.getDecision();
    if (decision == null) {
      return null;
    }

    IsNullValue inv = decision.getDecision(edge.getType());
    if (inv == null || !inv.isDefinitelyNotNull()) {
      return null;
    }
    ValueNumber value = decision.getValue();
    if (DEBUG) {
      System.out.println("Value number " + value + " is known nonnull on " + edge);
    }

    return value;
  }

  /**
   * Determine whether dataflow should be propagated on given edge.
   *
   * @param edge the edge
   * @return true if dataflow should be propagated on the edge, false otherwise
   */
  private boolean isExceptionEdge(Edge edge) {
    boolean isExceptionEdge = edge.isExceptionEdge();
    if (isExceptionEdge) {
      if (DEBUG) {
        System.out.println("NOT Ignoring " + edge);
      }
      return true; // false
    }
    if (edge.getType() != EdgeTypes.FALL_THROUGH_EDGE) {
      return false;
    }
    InstructionHandle h = edge.getSource().getLastInstruction();
    if (h != null
        && h.getInstruction() instanceof IFNONNULL
        && isNullCheck(h, methodGen.getConstantPool())) {
      return true;
    }

    return false;
  }

  @Override
  public boolean same(UnconditionalValueDerefSet fact1, UnconditionalValueDerefSet fact2) {
    return fact1.resultsFromBackEdge || fact1.isSameAs(fact2);
  }

  @Override
  public void startIteration() {
    // System.out.println("analysis iteration in " +
    // methodGen.getClassName() + " on " + methodGen.toString());
  }

  @Override
  public int getLastUpdateTimestamp(UnconditionalValueDerefSet fact) {
    return fact.getLastUpdateTimestamp();
  }

  @Override
  public void setLastUpdateTimestamp(UnconditionalValueDerefSet fact, int lastUpdate) {
    fact.setLastUpdateTimestamp(lastUpdate);
  }

  // public static void main(String[] args) throws Exception {
  // if (args.length != 1) {
  // System.err.println("Usage: " +
  // UnconditionalValueDerefAnalysis.class.getName() + " <classfile>");
  // System.exit(1);
  // }
  //
  // DataflowTestDriver<UnconditionalValueDerefSet,
  // UnconditionalValueDerefAnalysis> driver =
  // new DataflowTestDriver<UnconditionalValueDerefSet,
  // UnconditionalValueDerefAnalysis>() {
  // /* (non-Javadoc)
  // * @see
  // edu.umd.cs.findbugs.ba.DataflowTestDriver#createDataflow(edu.umd.cs.findbugs.ba.ClassContext,
  // org.apache.bcel.classfile.Method)
  // */
  // @Override
  // public Dataflow<UnconditionalValueDerefSet,
  // UnconditionalValueDerefAnalysis> createDataflow(ClassContext
  // classContext, Method method) throws CFGBuilderException,
  // DataflowAnalysisException {
  // return classContext.getUnconditionalValueDerefDataflow(method);
  // }
  // };
  // if (SystemProperties.getBoolean("forwardcfg")) {
  // driver.overrideIsForwards();
  // }
  // driver.execute(args[0]);
  // }
}
Beispiel #8
0
/**
 * Class to open input streams on source files. It maintains a "source path", which is like a
 * classpath, but for finding source files instead of class files.
 */
public class SourceFinder {
  private static final boolean DEBUG = SystemProperties.getBoolean("srcfinder.debug");

  private static final int CACHE_SIZE = 50;

  /*
   * ----------------------------------------------------------------------
   * Helper classes
   * ----------------------------------------------------------------------
   */

  /**
   * Cache of SourceFiles. We use this to avoid repeatedly having to read frequently accessed source
   * files.
   */
  private static class Cache extends LinkedHashMap<String, SourceFile> {
    /** */
    private static final long serialVersionUID = 1L;

    @Override
    protected boolean removeEldestEntry(Map.Entry<String, SourceFile> eldest) {
      return size() >= CACHE_SIZE;
    }
  }

  /** A repository of source files. */
  private interface SourceRepository {
    public boolean contains(String fileName);

    public boolean isPlatformDependent();

    public SourceFileDataSource getDataSource(String fileName);
  }

  /** A directory containing source files. */
  private static class DirectorySourceRepository implements SourceRepository {
    private String baseDir;

    public DirectorySourceRepository(String baseDir) {
      this.baseDir = baseDir;
    }

    @Override
    public String toString() {
      return "DirectorySourceRepository:" + baseDir;
    }

    public boolean contains(String fileName) {
      File file = new File(getFullFileName(fileName));
      boolean exists = file.exists();
      if (DEBUG) System.out.println("Exists " + exists + " for " + file);
      return exists;
    }

    public boolean isPlatformDependent() {
      return true;
    }

    public SourceFileDataSource getDataSource(String fileName) {
      return new FileSourceFileDataSource(getFullFileName(fileName));
    }

    private String getFullFileName(String fileName) {
      return baseDir + File.separator + fileName;
    }
  }

  private static class InMemorySourceRepository implements SourceRepository {

    Map<String, byte[]> contents = new HashMap<String, byte[]>();
    Map<String, Long> lastModified = new HashMap<String, Long>();

    InMemorySourceRepository(@WillClose ZipInputStream in) throws IOException {
      try {
        while (true) {

          ZipEntry e = in.getNextEntry();
          if (e == null) break;
          if (!e.isDirectory()) {
            String name = e.getName();
            long size = e.getSize();

            if (size > Integer.MAX_VALUE)
              throw new IOException(name + " is too big at " + size + " bytes");
            ByteArrayOutputStream out;
            if (size <= 0) out = new ByteArrayOutputStream();
            else out = new ByteArrayOutputStream((int) size);
            GZIPOutputStream gOut = new GZIPOutputStream(out);
            IO.copy(in, gOut);
            gOut.close();
            byte data[] = out.toByteArray();
            contents.put(name, data);
            lastModified.put(name, e.getTime());
          }
          in.closeEntry();
        }
      } finally {
        Util.closeSilently(in);
      }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.ba.SourceFinder.SourceRepository#contains(java
     * .lang.String)
     */
    public boolean contains(String fileName) {
      return contents.containsKey(fileName);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.ba.SourceFinder.SourceRepository#getDataSource
     * (java.lang.String)
     */
    public SourceFileDataSource getDataSource(final String fileName) {
      return new SourceFileDataSource() {

        public String getFullFileName() {
          return fileName;
        }

        public InputStream open() throws IOException {
          return new GZIPInputStream(new ByteArrayInputStream(contents.get(fileName)));
        }

        public long getLastModified() {
          Long when = lastModified.get(fileName);
          if (when == null || when < 0) return 0;
          return when;
        }
      };
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.ba.SourceFinder.SourceRepository#isPlatformDependent
     * ()
     */
    public boolean isPlatformDependent() {
      return false;
    }
  }

  SourceRepository makeInMemorySourceRepository(final String url) {
    final BlockingSourceRepository r = new BlockingSourceRepository();
    Util.runInDameonThread(
        new Runnable() {

          public void run() {
            InputStream in = null;
            try {

              URLConnection connection = new URL(url).openConnection();
              in = connection.getInputStream();
              if (getProject().isGuiAvaliable())
                in =
                    getProject()
                        .getGuiCallback()
                        .getProgressMonitorInputStream(
                            in,
                            connection.getContentLength(),
                            "Downloading project source code...");

              if (url.endsWith(".z0p.gz")) in = new GZIPInputStream(in);

              r.setBase(new InMemorySourceRepository(new ZipInputStream(in)));

            } catch (IOException e) {
              if (getProject().isGuiAvaliable()) {
                getProject()
                    .getGuiCallback()
                    .setErrorMessage("Unable to load " + url + "; " + e.getMessage());
              }
              AnalysisContext.logError("Unable to load " + url, e);
              Util.closeSilently(in);
            }
          }
        },
        "Source loading thread");
    return r;
  }

  SourceRepository makeJarURLConnectionSourceRepository(final String url)
      throws MalformedURLException, IOException {
    final File file = File.createTempFile("jar_cache", null);
    file.deleteOnExit();
    final BlockingSourceRepository r = new BlockingSourceRepository();
    Util.runInDameonThread(
        new Runnable() {

          public void run() {
            InputStream in = null;
            OutputStream out = null;
            try {
              URLConnection connection = new URL(url).openConnection();
              if (getProject().isGuiAvaliable()) {
                int size = connection.getContentLength();
                in =
                    getProject()
                        .getGuiCallback()
                        .getProgressMonitorInputStream(
                            connection.getInputStream(), size, "Loading source via url");
              } else {
                in = connection.getInputStream();
              }
              out = new FileOutputStream(file);
              IO.copy(in, out);
              r.setBase(new ZipSourceRepository(new ZipFile(file)));
            } catch (IOException e) {
              assert true;
            } finally {
              Util.closeSilently(in);
              Util.closeSilently(out);
            }
          }
        },
        "Source loading thread");
    return r;
  }

  static class BlockingSourceRepository implements SourceRepository {
    SourceRepository base;

    final CountDownLatch ready = new CountDownLatch(1);

    public BlockingSourceRepository() {}

    public boolean isReady() {
      return ready.getCount() == 0;
    }

    public void setBase(SourceRepository base) {
      this.base = base;
      ready.countDown();
    }

    private void await() {
      try {
        ready.await();
      } catch (InterruptedException e) {
        throw new IllegalStateException("Unexpected interrupt", e);
      }
    }

    public boolean contains(String fileName) {
      await();
      return base.contains(fileName);
    }

    public SourceFileDataSource getDataSource(String fileName) {
      await();
      return base.getDataSource(fileName);
    }

    public boolean isPlatformDependent() {
      await();
      return base.isPlatformDependent();
    }
  }

  /** A zip or jar archive containing source files. */
  static class ZipSourceRepository implements SourceRepository {
    ZipFile zipFile;

    public ZipSourceRepository(ZipFile zipFile) {
      this.zipFile = zipFile;
    }

    public boolean contains(String fileName) {
      return zipFile.getEntry(fileName) != null;
    }

    public boolean isPlatformDependent() {
      return false;
    }

    public SourceFileDataSource getDataSource(String fileName) {
      return new ZipSourceFileDataSource(zipFile, fileName);
    }
  }

  /*
   * ----------------------------------------------------------------------
   * Fields
   * ----------------------------------------------------------------------
   */

  private List<SourceRepository> repositoryList;

  private Cache cache;

  private Project project;

  /*
   * ----------------------------------------------------------------------
   * Public methods
   * ----------------------------------------------------------------------
   */

  public SourceFinder(Project project) {
    setProject(project);
  }

  /** @return Returns the project. */
  public Project getProject() {
    return project;
  }

  /** Set the list of source directories. */
  void setSourceBaseList(Iterable<String> sourceBaseList) {
    for (String repos : sourceBaseList) {
      if (repos.endsWith(".zip") || repos.endsWith(".jar") || repos.endsWith(".z0p.gz")) {
        // Zip or jar archive
        try {
          if (repos.startsWith("http:")
              || repos.startsWith("https:")
              || repos.startsWith("file:")) {
            String url = SystemProperties.rewriteURLAccordingToProperties(repos);
            repositoryList.add(makeInMemorySourceRepository(url));
          } else repositoryList.add(new ZipSourceRepository(new ZipFile(repos)));
        } catch (IOException e) {
          // Ignored - we won't use this archive
          AnalysisContext.logError("Unable to load " + repos, e);
        }
      } else {
        File dir = new File(repos);
        if (dir.canRead() && dir.isDirectory())
          repositoryList.add(new DirectorySourceRepository(repos));
        else {
          AnalysisContext.logError("Unable to load " + repos);
        }
      }
    }
  }

  /**
   * Open an input stream on a source file in given package.
   *
   * @param packageName the name of the package containing the class whose source file is given
   * @param fileName the unqualified name of the source file
   * @return an InputStream on the source file
   * @throws IOException if a matching source file cannot be found
   */
  public InputStream openSource(String packageName, String fileName) throws IOException {
    SourceFile sourceFile = findSourceFile(packageName, fileName);
    return sourceFile.getInputStream();
  }

  public InputStream openSource(SourceLineAnnotation source) throws IOException {
    SourceFile sourceFile = findSourceFile(source);
    return sourceFile.getInputStream();
  }

  public SourceFile findSourceFile(SourceLineAnnotation source) throws IOException {
    return findSourceFile(source.getPackageName(), getOrGuessSourceFile(source));
  }

  /**
   * Open a source file in given package.
   *
   * @param packageName the name of the package containing the class whose source file is given
   * @param fileName the unqualified name of the source file
   * @return the source file
   * @throws IOException if a matching source file cannot be found
   */
  public SourceFile findSourceFile(String packageName, String fileName) throws IOException {
    // On windows the fileName specification is different between a file in
    // a directory tree, and a
    // file in a zip file. In a directory tree the separator used is '\',
    // while in a zip it's '/'
    // Therefore for each repository figure out what kind it is and use the
    // appropriate separator.

    // In all practicality, this code could just use the hardcoded '/' char,
    // as windows can open
    // files with this separator, but to allow for the mythical 'other'
    // platform that uses an
    // alternate separator, make a distinction

    String platformName = getPlatformName(packageName, fileName);
    String canonicalName = getCanonicalName(packageName, fileName);

    // Is the file in the cache already? Always cache it with the canonical
    // name
    SourceFile sourceFile = cache.get(canonicalName);
    if (sourceFile != null) return sourceFile;

    // Find this source file, add its data to the cache
    if (DEBUG) System.out.println("Trying " + fileName + " in package " + packageName + "...");
    // Query each element of the source path to find the requested source
    // file
    for (SourceRepository repos : repositoryList) {
      if (repos instanceof BlockingSourceRepository
          && !((BlockingSourceRepository) repos).isReady()) continue;
      fileName = repos.isPlatformDependent() ? platformName : canonicalName;
      if (DEBUG) System.out.println("Looking in " + repos + " for " + fileName);
      if (repos.contains(fileName)) {
        // Found it
        sourceFile = new SourceFile(repos.getDataSource(fileName));
        cache.put(canonicalName, sourceFile); // always cache with
        // canonicalName
        return sourceFile;
      }
    }

    throw new FileNotFoundException("Can't find source file " + fileName);
  }

  /**
   * @param packageName
   * @param fileName
   * @return
   */
  public static String getPlatformName(String packageName, String fileName) {
    String platformName =
        packageName.replace('.', File.separatorChar)
            + (packageName.length() > 0 ? File.separator : "")
            + fileName;
    return platformName;
  }

  public static String getPlatformName(SourceLineAnnotation source) {
    return getPlatformName(source.getPackageName(), getOrGuessSourceFile(source));
  }

  public static String getCanonicalName(SourceLineAnnotation source) {
    return getCanonicalName(source.getPackageName(), getOrGuessSourceFile(source));
  }
  /**
   * @param packageName
   * @param fileName
   * @return
   */
  public static String getCanonicalName(String packageName, String fileName) {
    String canonicalName =
        packageName.replace('.', '/') + (packageName.length() > 0 ? "/" : "") + fileName;
    return canonicalName;
  }

  public static String getOrGuessSourceFile(SourceLineAnnotation source) {
    if (source.isSourceFileKnown()) return source.getSourceFile();
    String baseClassName = source.getClassName();
    int i = baseClassName.lastIndexOf('.');
    baseClassName = baseClassName.substring(i + 1);
    int j = baseClassName.indexOf("$");
    if (j >= 0) baseClassName = baseClassName.substring(0, j);
    return baseClassName + ".java";
  }

  public boolean hasSourceFile(SourceLineAnnotation source) {
    return hasSourceFile(source.getPackageName(), getOrGuessSourceFile(source));
  }

  public boolean hasSourceFile(String packageName, String fileName) {
    // On windows the fileName specification is different between a file in
    // a directory tree, and a
    // file in a zip file. In a directory tree the separator used is '\',
    // while in a zip it's '/'
    // Therefore for each repository figure out what kind it is and use the
    // appropriate separator.

    // In all practicality, this code could just use the hardcoded '/' char,
    // as windows can open
    // files with this separator, but to allow for the mythical 'other'
    // platform that uses an
    // alternate separator, make a distinction

    // Create a fully qualified source filename using the package name for
    // both directories and zips
    String platformName = getPlatformName(packageName, fileName);
    String canonicalName = getCanonicalName(packageName, fileName);

    // Is the file in the cache already? Always cache it with the canonical
    // name
    SourceFile sourceFile = cache.get(canonicalName);
    if (sourceFile != null) return true;

    // Find this source file, add its data to the cache
    if (DEBUG) System.out.println("Trying " + fileName + " in package " + packageName + "...");
    // Query each element of the source path to find the requested source
    // file
    for (SourceRepository repos : repositoryList) {
      if (repos instanceof BlockingSourceRepository
          && !((BlockingSourceRepository) repos).isReady()) continue;
      fileName = repos.isPlatformDependent() ? platformName : canonicalName;
      if (DEBUG) System.out.println("Looking in " + repos + " for " + fileName);
      if (repos.contains(fileName)) {
        return true;
      }
    }

    return false;
  }

  /** @param project */
  private void setProject(Project project) {
    this.project = project;
    repositoryList = new LinkedList<SourceRepository>();
    cache = new Cache();
    setSourceBaseList(project.getResolvedSourcePaths());
  }
}
 public CallToUnconditionalThrower(BugReporter bugReporter) {
   this.bugReporter = bugReporter;
   testingEnabled = SystemProperties.getBoolean("report_TESTING_pattern_in_standard_detectors");
 }
/**
 * Find unsatisfied obligations in Java methods. Examples: open streams, open database connections,
 * etc.
 *
 * <p>See Weimer and Necula, <a href="http://doi.acm.org/10.1145/1028976.1029011" >Finding and
 * preventing run-time error handling mistakes</a>, OOPSLA 2004.
 *
 * @author David Hovemeyer
 */
public class FindUnsatisfiedObligation extends CFGDetector {

  private static final boolean DEBUG = SystemProperties.getBoolean("oa.debug");

  private static final String DEBUG_METHOD = SystemProperties.getProperty("oa.method");

  private static final boolean DEBUG_FP = SystemProperties.getBoolean("oa.debug.fp");

  /**
   * Compute possible obligation transfers as a way of suppressing false positives due to "wrapper"
   * objects. Not quite ready for prime time.
   */
  private static final boolean COMPUTE_TRANSFERS =
      SystemProperties.getBoolean("oa.transfers", true);

  /**
   * Report path information from point of resource creation to CFG exit. This makes the reported
   * warning a lot easier to understand.
   */
  private static final boolean REPORT_PATH = SystemProperties.getBoolean("oa.reportpath", true);

  private static final boolean REPORT_PATH_DEBUG =
      SystemProperties.getBoolean("oa.reportpath.debug");

  /** Report the final obligation set as part of the BugInstance. */
  private static final boolean REPORT_OBLIGATION_SET =
      SystemProperties.getBoolean("oa.report.obligationset", true);

  private final BugReporter bugReporter;

  private ObligationPolicyDatabase database;

  public FindUnsatisfiedObligation(BugReporter bugReporter) {
    this.bugReporter = bugReporter;
    IAnalysisCache analysisCache = Global.getAnalysisCache();

    database = analysisCache.getDatabase(ObligationPolicyDatabase.class);
  }

  @Override
  public void visitClass(ClassDescriptor classDescriptor) throws CheckedAnalysisException {
    IAnalysisCache analysisCache = Global.getAnalysisCache();

    ObligationFactory factory = database.getFactory();

    JavaClass jclass = analysisCache.getClassAnalysis(JavaClass.class, classDescriptor);
    for (Constant c : jclass.getConstantPool().getConstantPool()) {
      if (c instanceof ConstantNameAndType) {
        ConstantNameAndType cnt = (ConstantNameAndType) c;
        String signature = cnt.getSignature(jclass.getConstantPool());
        if (factory.signatureInvolvesObligations(signature)) {
          super.visitClass(classDescriptor);
          return;
        }
      } else if (c instanceof ConstantClass) {
        String className = ((ConstantClass) c).getBytes(jclass.getConstantPool());
        if (factory.signatureInvolvesObligations(className)) {
          super.visitClass(classDescriptor);
          return;
        }
      }
    }
    if (DEBUG) System.out.println(classDescriptor + " isn't interesting for obligation analysis");
  }

  @Override
  protected void visitMethodCFG(MethodDescriptor methodDescriptor, CFG cfg)
      throws CheckedAnalysisException {

    MethodChecker methodChecker = new MethodChecker(methodDescriptor, cfg);
    methodChecker.analyzeMethod();
  }

  /**
   * Helper class to keep track of possible obligation transfers observed along paths where an
   * obligation appears to be leaked.
   */
  private static class PossibleObligationTransfer {
    Obligation consumed, produced;

    public PossibleObligationTransfer(@Nonnull Obligation consumed, @Nonnull Obligation produced) {
      this.consumed = consumed;
      this.produced = produced;
    }

    /**
     * Determine whether the state has "balanced" obligation counts for the consumed and produced
     * Obligation types.
     *
     * @param state a State
     * @return true if the obligation counts are balanced, false otherwise
     */
    private boolean balanced(State state) {
      int consumedCount = state.getObligationSet().getCount(consumed.getId());
      int producedCount = state.getObligationSet().getCount(produced.getId());
      return (consumedCount + producedCount == 0) && (consumedCount == 1 || producedCount == 1);
    }

    private boolean matches(Obligation possiblyLeakedObligation) {
      return consumed.equals(possiblyLeakedObligation) || produced.equals(possiblyLeakedObligation);
    }

    @Override
    public String toString() {
      return consumed + " -> " + produced;
    }
  }

  /**
   * A helper class to check a single method for unsatisfied obligations. Avoids having to pass
   * millions of parameters to each method (type dataflow, null value dataflow, etc.).
   */
  private class MethodChecker {
    MethodDescriptor methodDescriptor;

    CFG cfg;

    IAnalysisCache analysisCache;

    ObligationDataflow dataflow;

    ConstantPoolGen cpg;

    TypeDataflow typeDataflow;

    Subtypes2 subtypes2;

    XMethod xmethod;

    MethodChecker(MethodDescriptor methodDescriptor, CFG cfg) {
      this.methodDescriptor = methodDescriptor;
      this.cfg = cfg;
    }

    public void analyzeMethod() throws CheckedAnalysisException {
      if (DEBUG_METHOD != null && !methodDescriptor.getName().equals(DEBUG_METHOD)) {
        return;
      }

      if (DEBUG) {
        System.out.println("*** Analyzing method " + methodDescriptor);
      }

      xmethod = XFactory.createXMethod(methodDescriptor);
      analysisCache = Global.getAnalysisCache();

      //
      // Execute the obligation dataflow analysis
      //
      try {
        dataflow = analysisCache.getMethodAnalysis(ObligationDataflow.class, methodDescriptor);
      } catch (ObligationAcquiredOrReleasedInLoopException e) {
        // It is not possible to analyze this method.
        if (DEBUG) {
          System.out.println(
              "FindUnsatisifedObligation: " + methodDescriptor + ": " + e.getMessage());
        }
        return;
      }

      //
      // Additional analyses
      // needed these to apply the false-positive
      // suppression heuristics.
      //
      cpg =
          analysisCache.getClassAnalysis(
              ConstantPoolGen.class, methodDescriptor.getClassDescriptor());
      typeDataflow = analysisCache.getMethodAnalysis(TypeDataflow.class, methodDescriptor);
      subtypes2 = Global.getAnalysisCache().getDatabase(Subtypes2.class);

      //
      // Main loop: looking at the StateSet at the exit block of the CFG,
      // see if there are any states with nonempty obligation sets.
      //
      Map<Obligation, State> leakedObligationMap = new HashMap<Obligation, State>();
      StateSet factAtExit = dataflow.getResultFact(cfg.getExit());
      for (Iterator<State> i = factAtExit.stateIterator(); i.hasNext(); ) {
        State state = i.next();
        checkStateForLeakedObligations(state, leakedObligationMap);
      }

      //
      // Report a separate BugInstance for each Obligation,State pair.
      // (Two different obligations may be leaked in the same state.)
      //
      for (Map.Entry<Obligation, State> entry : leakedObligationMap.entrySet()) {
        Obligation obligation = entry.getKey();
        State state = entry.getValue();
        reportWarning(obligation, state, factAtExit);
      }
      // TODO: closing of nonexistent resources

    }

    private void checkStateForLeakedObligations(
        State state, Map<Obligation, State> leakedObligationMap) throws IllegalStateException {
      if (DEBUG) {
        Path path = state.getPath();
        if (path.getLength() > 0
            && path.getBlockIdAt(path.getLength() - 1) != cfg.getExit().getLabel()) {
          throw new IllegalStateException(
              "path " + path + " at cfg exit has no label for exit block");
        }
      }

      for (int id = 0; id < database.getFactory().getMaxObligationTypes(); ++id) {
        Obligation obligation = database.getFactory().getObligationById(id);
        // If the raw count produced by the analysis
        // for this obligation type is 0,
        // assume everything is ok on this state's path.
        int rawLeakCount = state.getObligationSet().getCount(id);
        if (rawLeakCount == 0) {
          continue;
        }

        // Apply the false-positive suppression heuristics
        int leakCount = getAdjustedLeakCount(state, id);

        if (leakCount > 0) {
          leakedObligationMap.put(obligation, state);
        }
        // TODO: if the leak count is less than 0, then a nonexistent
        // resource was closed
      }
    }

    private void reportWarning(Obligation obligation, State state, StateSet factAtExit) {
      String className = obligation.getClassName();

      if (methodDescriptor.isStatic()
          && methodDescriptor.getName().equals("main")
          && methodDescriptor.getSignature().equals("([Ljava/lang/String;)V")
          && (className.contains("InputStream")
              || className.contains("Reader")
              || factAtExit.isOnExceptionPath())) {
        // Don't report unclosed input streams and readers in main()
        // methods
        return;
      }
      String bugPattern =
          factAtExit.isOnExceptionPath()
              ? "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE"
              : "OBL_UNSATISFIED_OBLIGATION";
      BugInstance bugInstance =
          new BugInstance(FindUnsatisfiedObligation.this, bugPattern, NORMAL_PRIORITY)
              .addClassAndMethod(methodDescriptor)
              .addClass(className)
              .describe("CLASS_REFTYPE");

      // Report how many instances of the obligation are remaining
      bugInstance
          .addInt(state.getObligationSet().getCount(obligation.getId()))
          .describe(IntAnnotation.INT_OBLIGATIONS_REMAINING);

      // Add source line information
      annotateWarningWithSourceLineInformation(state, obligation, bugInstance);

      if (REPORT_OBLIGATION_SET) {
        bugInstance
            .addString(state.getObligationSet().toString())
            .describe(StringAnnotation.REMAINING_OBLIGATIONS_ROLE);
      }

      bugReporter.reportBug(bugInstance);
    }

    private void annotateWarningWithSourceLineInformation(
        State state, Obligation obligation, BugInstance bugInstance) {
      // The reportPath() method currently does all reporting
      // of source line information.
      if (REPORT_PATH) {
        reportPath(bugInstance, obligation, state);
      }
    }

    /**
     * Helper class to apply the false-positive suppression heuristics along a Path where an
     * obligation leak might have occurred.
     */
    private class PostProcessingPathVisitor implements PathVisitor {
      Obligation possiblyLeakedObligation;

      State state;

      int adjustedLeakCount;

      BasicBlock curBlock;

      boolean couldNotAnalyze;

      List<PossibleObligationTransfer> transferList;

      public PostProcessingPathVisitor(Obligation possiblyLeakedObligation /*
                                                                                 * ,
                                                                                 * int
                                                                                 * initialLeakCount
                                                                                 */, State state) {
        this.possiblyLeakedObligation = possiblyLeakedObligation;
        this.state = state;
        this.adjustedLeakCount =
            state.getObligationSet().getCount(possiblyLeakedObligation.getId());
        if (COMPUTE_TRANSFERS) {
          this.transferList = new LinkedList<PossibleObligationTransfer>();
        }
      }

      public int getAdjustedLeakCount() {
        return adjustedLeakCount;
      }

      public boolean couldNotAnalyze() {
        return couldNotAnalyze;
      }

      @Override
      public void visitBasicBlock(BasicBlock basicBlock) {
        curBlock = basicBlock;

        if (COMPUTE_TRANSFERS && basicBlock == cfg.getExit()) {
          // We're at the CFG exit.

          if (adjustedLeakCount == 1) {
            applyPossibleObligationTransfers();
          }
        }
      }

      @Override
      public void visitInstructionHandle(InstructionHandle handle) {
        try {
          Instruction ins = handle.getInstruction();
          short opcode = ins.getOpcode();
          if (DEBUG)
            System.out.printf("%3d %s%n", handle.getPosition(), Constants.OPCODE_NAMES[opcode]);

          if (opcode == Constants.PUTFIELD
              || opcode == Constants.PUTSTATIC
              || opcode == Constants.ARETURN) {
            //
            // A value is being assigned to a field or returned from
            // the method.
            //
            Location loc = new Location(handle, curBlock);
            TypeFrame typeFrame = typeDataflow.getFactAtLocation(loc);
            if (!typeFrame.isValid()) {
              // dead code?
              couldNotAnalyze = true;
            }
            Type tosType = typeFrame.getTopValue();
            if (tosType instanceof ObjectType
                && isPossibleInstanceOfObligationType(
                    subtypes2, (ObjectType) tosType, possiblyLeakedObligation.getType())) {
              // Remove one obligation of this type
              adjustedLeakCount--;
              if (DEBUG)
                System.out.println(
                    "removing obligation to close " + tosType + " at " + handle.getPosition());
            }
          }

          if (COMPUTE_TRANSFERS && ins instanceof InvokeInstruction) {
            checkForPossibleObligationTransfer((InvokeInstruction) ins, handle);
          }
        } catch (ClassNotFoundException e) {
          bugReporter.reportMissingClass(e);
          couldNotAnalyze = true;
        } catch (DataflowAnalysisException e) {
          couldNotAnalyze = true;
        }
      }

      private void applyPossibleObligationTransfers() {
        //
        // See if we recorded any possible obligation transfers
        // that might have created a "wrapper" object.
        // In many cases, it is correct to close either
        // the "wrapped" or "wrapper" object.
        // So, if we see a possible transfer, and we see
        // a +1/-1 obligation count for the pair
        // (consumed and produced obligation types),
        // rather than 0/0,
        // then we will assume that which resource was closed
        // (wrapper or wrapped) was the opposite of what
        // we expected.
        //
        for (PossibleObligationTransfer transfer : transferList) {
          if (DEBUG_FP) {
            System.out.println("Checking possible transfer " + transfer + "...");
          }

          boolean matches = transfer.matches(possiblyLeakedObligation);

          if (DEBUG_FP) {
            System.out.println("  matches: " + possiblyLeakedObligation);
          }

          if (matches) {
            boolean balanced = transfer.balanced(state);
            if (DEBUG_FP) {
              System.out.println("  balanced: " + balanced + " in " + state.getObligationSet());
            }
            if (balanced) {
              if (DEBUG_FP) {
                System.out.println(
                    "  Suppressing path because "
                        + "a transfer appears to result in balanced "
                        + "outstanding obligations");
              }

              adjustedLeakCount = 0;
              break;
            }
          }
        }
      }

      private void checkForPossibleObligationTransfer(
          InvokeInstruction inv, InstructionHandle handle) throws ClassNotFoundException {
        //
        // We will assume that a method invocation might transfer
        // an obligation from one type to another if
        // 1. either
        // - it's a constructor where the constructed
        // type and exactly one param type
        // are obligation types, or
        // - it's a method where the return type and
        // exactly one param type are obligation types
        // 2. at least one instance of the resource "consumed"
        // by the transfer exists at the point of the transfer.
        // E.g., if we see a transfer of InputStream->Reader,
        // there must be an instance of InputStream at
        // the transfer point.
        //

        if (DEBUG_FP) {
          System.out.println("Checking " + handle + " as possible obligation transfer...:");
        }

        // Find the State which is a prefix of the error state
        // at the location of this (possible) transfer.
        State transferState = getTransferState(handle);
        if (transferState == null) {
          if (DEBUG_FP) {
            System.out.println("No transfer state???");
          }
          return;
        }

        String methodName = inv.getMethodName(cpg);
        Type producedType =
            methodName.equals("<init>") ? inv.getReferenceType(cpg) : inv.getReturnType(cpg);

        if (DEBUG_FP && !(producedType instanceof ObjectType)) {
          System.out.println("Produced type " + producedType + " not an ObjectType");
        }

        if (producedType instanceof ObjectType) {
          Obligation produced =
              database.getFactory().getObligationByType((ObjectType) producedType);

          if (DEBUG_FP && produced == null) {
            System.out.println("Produced type  " + producedType + " not an obligation type");
          }

          if (produced != null) {
            XMethod calledMethod = XFactory.createXMethod(inv, cpg);
            Obligation[] params = database.getFactory().getParameterObligationTypes(calledMethod);

            for (int i = 0; i < params.length; i++) {
              Obligation consumed = params[i];

              if (DEBUG_FP && consumed == null) {
                System.out.println("Param " + i + " not an obligation type");
              }

              if (DEBUG_FP && consumed != null && consumed.equals(produced)) {
                System.out.println("Consumed type is the same as produced type");
              }

              if (consumed != null && !consumed.equals(produced)) {
                // See if an instance of the consumed obligation
                // type
                // exists here.
                if (transferState.getObligationSet().getCount(consumed.getId()) > 0) {
                  transferList.add(new PossibleObligationTransfer(consumed, produced));
                  if (DEBUG_FP) {
                    System.out.println(
                        "===> Possible transfer of "
                            + consumed
                            + " to "
                            + produced
                            + " at "
                            + handle);
                  }
                } else if (DEBUG_FP) {
                  System.out.println(
                      handle
                          + " not a transfer "
                          + "of "
                          + consumed
                          + "->"
                          + produced
                          + " because no instances of "
                          + consumed);
                  System.out.println("I see " + transferState.getObligationSet());
                }
              }
            }
          }
        }
      }

      @Override
      public void visitEdge(Edge edge) {
        if (DEBUG_FP) {
          System.out.println("visit edge " + edge);
        }
      }

      private State getTransferState(InstructionHandle handle) {
        StateSet stateSet;
        try {
          stateSet = dataflow.getFactAtLocation(new Location(handle, curBlock));
        } catch (DataflowAnalysisException e) {
          bugReporter.logError("Error checking obligation state at " + handle, e);
          return null;
        }

        List<State> prefixes = stateSet.getPrefixStates(state.getPath());
        if (prefixes.size() != 1) {
          // Could this happen?
          if (DEBUG_FP) {
            System.out.println(
                "at "
                    + handle
                    + " in "
                    + xmethod
                    + " found "
                    + prefixes.size()
                    + " states which are prefixes of error state");
          }
          return null;
        }

        return prefixes.get(0);
      }
    }

    /**
     * Get the adjusted leak count for the given State and obligation type. Use heuristics to
     * account for:
     *
     * <ul>
     *   <li>null checks (count the number of times the supposedly leaked obligation is compared to
     *       null, and subtract those from the leak count)
     *   <li>field assignments (count number of times obligation type is assigned to a field, and
     *       subtract those from the leak count)
     *   <li>return statements (if an instance of the obligation type is returned from the method,
     *       subtract one from leak count)
     * </ul>
     *
     * @return the adjusted leak count (positive if leaked obligation, negative if attempt to
     *     release an un-acquired obligation)
     */
    private int getAdjustedLeakCount(State state, int obligationId) {

      final Obligation obligation = database.getFactory().getObligationById(obligationId);
      Path path = state.getPath();
      PostProcessingPathVisitor visitor = new PostProcessingPathVisitor(obligation, state);
      path.acceptVisitor(cfg, visitor);

      if (visitor.couldNotAnalyze()) {
        return 0;
      } else {
        return visitor.getAdjustedLeakCount();
      }
    }

    private boolean isPossibleInstanceOfObligationType(
        Subtypes2 subtypes2, ObjectType type, ObjectType obligationType)
        throws ClassNotFoundException {
      //
      // If we're tracking, e.g., InputStream obligations,
      // and we see a FileInputStream reference being assigned
      // to a field (or returned from a method),
      // then the false-positive supressions heuristic should apply.
      //

      return subtypes2.isSubtype(type, obligationType);
    }

    private void reportPath(
        final BugInstance bugInstance, final Obligation obligation, final State state) {

      Path path = state.getPath();

      // This PathVisitor will traverse the Path and add appropriate
      // SourceLineAnnotations to the BugInstance.
      PathVisitor visitor =
          new PathVisitor() {
            boolean sawFirstCreation;

            SourceLineAnnotation lastSourceLine; // = creationSourceLine;

            BasicBlock curBlock;

            @Override
            public void visitBasicBlock(BasicBlock basicBlock) {
              curBlock = basicBlock;

              // See if the initial instance of the leaked resource
              // is in the entry fact due to a @WillClose annotation.
              if (curBlock == cfg.getEntry()) {
                // Get the entry fact - it should have precisely one
                // state
                StateSet entryFact = dataflow.getResultFact(curBlock);
                Iterator<State> i = entryFact.stateIterator();
                if (i.hasNext()) {
                  State entryState = i.next();
                  if (entryState.getObligationSet().getCount(obligation.getId()) > 0) {
                    lastSourceLine = SourceLineAnnotation.forFirstLineOfMethod(methodDescriptor);
                    lastSourceLine.setDescription(
                        SourceLineAnnotation.ROLE_OBLIGATION_CREATED_BY_WILLCLOSE_PARAMETER);
                    bugInstance.add(lastSourceLine);
                    sawFirstCreation = true;

                    if (REPORT_PATH_DEBUG) {
                      System.out.println(
                          "  "
                              + obligation
                              + " created by @WillClose parameter at "
                              + lastSourceLine);
                    }
                  }
                }
              }
            }

            @Override
            public void visitInstructionHandle(InstructionHandle handle) {
              boolean isCreation =
                  (dataflow
                      .getAnalysis()
                      .getActionCache()
                      .addsObligation(curBlock, handle, obligation));

              if (!sawFirstCreation && !isCreation) {
                return;
              }

              SourceLineAnnotation sourceLine =
                  SourceLineAnnotation.fromVisitedInstruction(
                      methodDescriptor, new Location(handle, curBlock));
              sourceLine.setDescription(
                  isCreation
                      ? SourceLineAnnotation.ROLE_OBLIGATION_CREATED
                      : SourceLineAnnotation.ROLE_PATH_CONTINUES);

              boolean isInteresting =
                  (sourceLine.getStartLine() > 0)
                      && (lastSourceLine == null
                          || isCreation
                          || sourceLine.getStartLine() != lastSourceLine.getStartLine());

              if (REPORT_PATH_DEBUG) {
                System.out.println(
                    "  "
                        + handle.getPosition()
                        + " --> "
                        + sourceLine
                        + (isInteresting ? " **" : ""));
              }
              if (isInteresting) {
                bugInstance.add(sourceLine);
                lastSourceLine = sourceLine;
                if (isCreation) {
                  sawFirstCreation = true;
                }
              }
            }

            @Override
            public void visitEdge(Edge edge) {
              if (REPORT_PATH_DEBUG) {
                System.out.println(
                    "Edge of type "
                        + Edge.edgeTypeToString(edge.getType())
                        + " to "
                        + edge.getTarget().getLabel());
                if (edge.getTarget().getFirstInstruction() != null) {
                  System.out.println(
                      "  First instruction in target: " + edge.getTarget().getFirstInstruction());
                }
                if (edge.getTarget().isExceptionThrower()) {
                  System.out.println(
                      "  exception thrower for " + edge.getTarget().getExceptionThrower());
                }
                if (edge.isExceptionEdge()) {
                  System.out.println(
                      "  exceptions thrown: " + typeDataflow.getEdgeExceptionSet(edge));
                }
              }
            }
          };

      // Visit the Path
      path.acceptVisitor(cfg, visitor);
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see edu.umd.cs.findbugs.Detector#report()
   */
  public void report() {
    // Nothing to do here
  }
}
/**
 * @author Nat Ayewah
 * @author William Pugh
 */
public class FindUnrelatedTypesInGenericContainer implements Detector {

  private final BugReporter bugReporter;

  private static final boolean DEBUG = SystemProperties.getBoolean("gc.debug");

  static class Info {
    public Info(ClassDescriptor interfaceForCall, int argumentIndex, int typeIndex) {
      this.interfaceForCall = interfaceForCall;
      this.argumentIndex = argumentIndex;
      this.typeIndex = typeIndex;
    }

    final ClassDescriptor interfaceForCall;
    final int argumentIndex;
    final int typeIndex;

    @Override
    public String toString() {
      return String.format("[%s %d %d]", interfaceForCall, argumentIndex, typeIndex);
    }
  }
  /**
   * Map classname, methodname and signature to an int []. Each position in the int [] corresponds
   * to an argument in the methodSignature. For each argument i, the value at position i corresponds
   * to the index of the corresponding type in the class type parameters. If the argument has no
   * correspondence, then the value is -1.
   *
   * <p>Get the String key by calling getCollectionsMapKey()
   */
  private final MultiMap<String, Info> callMap = new MultiMap<String, Info>(LinkedList.class);

  private void addCheckedCall(
      @DottedClassName String className,
      String methodName,
      String sig,
      int argumentParameterIndex,
      int typeParameterIndex) {
    ClassDescriptor c =
        DescriptorFactory.instance().getClassDescriptorForDottedClassName(className);
    String call = methodName + sig;
    Info info = new Info(c, argumentParameterIndex, typeParameterIndex);
    callMap.add(call, info);
  }

  private void addCheckedCall(
      @DottedClassName String className, String methodName, int typeParameterIndex) {
    addCheckedCall(className, methodName, "(Ljava/lang/Object;)", 0, typeParameterIndex);
  }

  public FindUnrelatedTypesInGenericContainer(BugReporter bugReporter) {
    this.bugReporter = bugReporter;

    // Collection<E>
    addCheckedCall(Collection.class.getName(), "contains", 0);
    addCheckedCall(Collection.class.getName(), "remove", 0);
    addCheckedCall(Collection.class.getName(), "containsAll", "(Ljava/util/Collection;)", 0, -1);
    addCheckedCall(Collection.class.getName(), "removeAll", "(Ljava/util/Collection;)", 0, -1);
    addCheckedCall(Collection.class.getName(), "retainAll", "(Ljava/util/Collection;)", 0, -1);

    // Dequeue<E>
    addCheckedCall("java.util.Deque", "removeFirstOccurrence", 0);
    addCheckedCall("java.util.Deque", "removeLastOccurrence", 0);

    // List<E>
    addCheckedCall(List.class.getName(), "indexOf", 0);
    addCheckedCall(List.class.getName(), "lastIndexOf", 0);

    // Vector<E>
    addCheckedCall(Vector.class.getName(), "indexOf", "(Ljava/lang/Object;I)", 0, 0);
    addCheckedCall(Vector.class.getName(), "lastIndexOf", "(Ljava/lang/Object;I)", 0, 0);

    // Map<K,V>
    addCheckedCall(Map.class.getName(), "containsKey", 0);
    addCheckedCall(Map.class.getName(), "containsValue", 1);
    addCheckedCall(Map.class.getName(), "get", 0);
    addCheckedCall(Map.class.getName(), "remove", 0);

    // Hashtable<K,V>
    addCheckedCall(Hashtable.class.getName(), "contains", 1);

    // ConcurrentHashMap<K,V>
    addCheckedCall(ConcurrentHashMap.class.getName(), "contains", 1);

    // ConcurrentMap<K,V>
    addCheckedCall(
        ConcurrentMap.class.getName(), "remove", "(Ljava/lang/Object;Ljava/lang/Object;)", 0, 0);
    addCheckedCall(
        ConcurrentMap.class.getName(), "remove", "(Ljava/lang/Object;Ljava/lang/Object;)", 1, 1);

    // Multimap<K,V>
    addCheckedCall(
        "com.google.common.collect.Multimap",
        "containsEntry",
        "(Ljava/lang/Object;Ljava/lang/Object;)",
        0,
        0);
    addCheckedCall(
        "com.google.common.collect.Multimap",
        "containsEntry",
        "(Ljava/lang/Object;Ljava/lang/Object;)",
        1,
        1);
    addCheckedCall("com.google.common.collect.Multimap", "containsKey", 0);
    addCheckedCall("com.google.common.collect.Multimap", "containsValue", 1);
    addCheckedCall(
        "com.google.common.collect.Multimap",
        "remove",
        "(Ljava/lang/Object;Ljava/lang/Object;)",
        0,
        0);
    addCheckedCall(
        "com.google.common.collect.Multimap",
        "remove",
        "(Ljava/lang/Object;Ljava/lang/Object;)",
        1,
        1);
    addCheckedCall("com.google.common.collect.Multimap", "removeAll", 0);

    // Cache<K,V>
    addCheckedCall("com.google.common.cache.Cache", "invalidate", 0);

    // Multiset<E>
    addCheckedCall("com.google.common.collect.Multiset", "count", 0);
    addCheckedCall("com.google.common.collect.Multiset", "remove", "(Ljava/lang/Object;I)", 0, 0);

    // Table<R,C,V>
    addCheckedCall(
        "com.google.common.collect.Table",
        "contains",
        "(Ljava/lang/Object;Ljava/lang/Object;)",
        0,
        0);
    addCheckedCall(
        "com.google.common.collect.Table",
        "contains",
        "(Ljava/lang/Object;Ljava/lang/Object;)",
        1,
        1);
    addCheckedCall("com.google.common.collect.Table", "containsRow", 0);
    addCheckedCall("com.google.common.collect.Table", "containsColumn", 1);
    addCheckedCall("com.google.common.collect.Table", "containsValue", 2);
    addCheckedCall(
        "com.google.common.collect.Table", "get", "(Ljava/lang/Object;Ljava/lang/Object;)", 0, 0);
    addCheckedCall(
        "com.google.common.collect.Table", "get", "(Ljava/lang/Object;Ljava/lang/Object;)", 1, 1);
    addCheckedCall(
        "com.google.common.collect.Table",
        "remove",
        "(Ljava/lang/Object;Ljava/lang/Object;)",
        0,
        0);
    addCheckedCall(
        "com.google.common.collect.Table",
        "remove",
        "(Ljava/lang/Object;Ljava/lang/Object;)",
        1,
        1);

    // Sets
    addCheckedCall(
        "com.google.common.collect.Sets",
        "intersection",
        "(Ljava/util/Set;Ljava/util/Set;)",
        1,
        -1);
    addCheckedCall(
        "com.google.common.collect.Sets", "difference", "(Ljava/util/Set;Ljava/util/Set;)", 1, -1);
    addCheckedCall(
        "com.google.common.collect.Sets",
        "symmetricDifference",
        "(Ljava/util/Set;Ljava/util/Set;)",
        1,
        -1);

    // Iterables
    addCheckedCall(
        "com.google.common.collect.Iterables",
        "contains",
        "(Ljava/lang/Iterable;Ljava/lang/Object;)",
        1,
        0);
    addCheckedCall(
        "com.google.common.collect.Iterables",
        "removeAll",
        "(Ljava/lang/Iterable;Ljava/util/Collection;)",
        1,
        -1);
    addCheckedCall(
        "com.google.common.collect.Iterables",
        "retainAll",
        "(Ljava/lang/Iterable;Ljava/util/Collection;)",
        1,
        -1);
    addCheckedCall(
        "com.google.common.collect.Iterables",
        "elementsEqual",
        "(Ljava/lang/Iterable;Ljava/lang/Iterable;)",
        1,
        -1);
    addCheckedCall(
        "com.google.common.collect.Iterables",
        "frequency",
        "(Ljava/lang/Iterable;Ljava/lang/Object;)",
        1,
        0);

    // Iterators
    addCheckedCall(
        "com.google.common.collect.Iterators",
        "contains",
        "(Ljava/util/Iterator;Ljava/lang/Object;)",
        1,
        0);
    addCheckedCall(
        "com.google.common.collect.Iterators",
        "removeAll",
        "(Ljava/util/Iterator;Ljava/util/Collection;)",
        1,
        -1);
    addCheckedCall(
        "com.google.common.collect.Iterators",
        "retainAll",
        "(Ljava/util/Iterator;Ljava/util/Collection;)",
        1,
        -1);
    addCheckedCall(
        "com.google.common.collect.Iterators",
        "elementsEqual",
        "(Ljava/util/Iterator;Ljava/util/Iterator;)",
        1,
        -1);
    addCheckedCall(
        "com.google.common.collect.Iterators",
        "frequency",
        "(Ljava/util/Iterator;Ljava/lang/Object;)",
        1,
        0);
  }

  /**
   * Visit the class context
   *
   * @see edu.umd.cs.findbugs.Detector#visitClassContext(edu.umd.cs.findbugs.ba.ClassContext)
   */
  public void visitClassContext(ClassContext classContext) {
    JavaClass javaClass = classContext.getJavaClass();
    Method[] methodList = javaClass.getMethods();

    for (Method method : methodList) {
      if (method.getCode() == null) continue;

      try {
        analyzeMethod(classContext, method);
      } catch (MethodUnprofitableException e) {
        assert true; // move along; nothing to see
      } catch (CFGBuilderException e) {
        String msg =
            "Detector "
                + this.getClass().getName()
                + " caught exception while analyzing "
                + javaClass.getClassName()
                + "."
                + method.getName()
                + " : "
                + method.getSignature();
        bugReporter.logError(msg, e);
      } catch (DataflowAnalysisException e) {
        String msg =
            "Detector "
                + this.getClass().getName()
                + " caught exception while analyzing "
                + javaClass.getClassName()
                + "."
                + method.getName()
                + " : "
                + method.getSignature();
        bugReporter.logError(msg, e);
      }
    }
  }

  /** Use this to screen out methods that do not contain invocations. */
  public boolean prescreen(ClassContext classContext, Method method) {
    BitSet bytecodeSet = classContext.getBytecodeSet(method);
    return bytecodeSet != null
        && (bytecodeSet.get(Constants.INVOKEINTERFACE)
            || bytecodeSet.get(Constants.INVOKEVIRTUAL)
            || bytecodeSet.get(Constants.INVOKESPECIAL)
            || bytecodeSet.get(Constants.INVOKESTATIC)
            || bytecodeSet.get(Constants.INVOKENONVIRTUAL));
  }

  /** Methods marked with the "Synthetic" attribute do not appear in the source code */
  private boolean isSynthetic(Method m) {
    if ((m.getAccessFlags() & Constants.ACC_SYNTHETIC) != 0) return true;
    Attribute[] attrs = m.getAttributes();
    for (Attribute attr : attrs) {
      if (attr instanceof Synthetic) return true;
    }
    return false;
  }

  static final LinkedHashSet<String> baseGenericTypes = new LinkedHashSet<String>();

  static {
    baseGenericTypes.addAll(
        Arrays.asList(
            new String[] {
              "java.util.Map",
              "java.util.Collection",
              "java.lang.Iterable",
              "java.util.Iterator",
              "com.google.common.collect.Multimap",
              "com.google.common.collect.Multiset",
              "com.google.common.collect.Table"
            }));
  }

  private boolean isGenericCollection(ClassDescriptor operandClass) {
    String dottedClassName = operandClass.getDottedClassName();

    if (baseGenericTypes.contains(dottedClassName)) return true;

    String found = null;
    for (String c : baseGenericTypes) {
      if (Subtypes2.instanceOf(operandClass, c)) {
        found = c;
        break;
      }
    }
    if (found == null) return false;
    if (dottedClassName.startsWith("java.util.")
        || dottedClassName.startsWith("com.google.common.collect.")) return true;
    try {
      XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, operandClass);

      String sig = xclass.getSourceSignature();
      if (sig == null) return false;

      String typeParameter = null;
      List<String> split = GenericUtilities.split(sig, true);
      if (sig.charAt(0) == '<') {
        int end = sig.indexOf(':');
        if (end > 0) typeParameter = sig.substring(1, end);
      }
      if (DEBUG) System.out.println(dottedClassName + " " + typeParameter + " " + split);
      for (String s : split) {
        int i = s.indexOf('<');
        if (i < 0) continue;
        if (s.charAt(0) != 'L') throw new IllegalStateException("unexpected non signature: " + s);
        ClassDescriptor c = DescriptorFactory.createClassDescriptor(s.substring(1, i));
        String superTypeParameter = s.substring(i + 1);
        if (isGenericCollection(c)
            && (typeParameter == null || superTypeParameter.startsWith("T" + typeParameter))) {
          if (DEBUG) System.out.println(operandClass + " is a subtype of " + s);
          return true;
        }
      }
      if (DEBUG) System.out.println("Not a subtype");

    } catch (CheckedAnalysisException e1) {
      AnalysisContext.logError(
          "Error checking for weird generic parameterization of " + operandClass, e1);
    }
    return false;
  }

  private void analyzeMethod(ClassContext classContext, Method method)
      throws CFGBuilderException, DataflowAnalysisException {
    if (isSynthetic(method) || !prescreen(classContext, method)) return;
    XMethod xmethod = XFactory.createXMethod(classContext.getJavaClass(), method);
    if (xmethod.isSynthetic()) return;

    BugAccumulator accumulator = new BugAccumulator(bugReporter);

    CFG cfg = classContext.getCFG(method);
    TypeDataflow typeDataflow = classContext.getTypeDataflow(method);
    ValueNumberDataflow vnDataflow = classContext.getValueNumberDataflow(method);

    ConstantPoolGen cpg = classContext.getConstantPoolGen();
    MethodGen methodGen = classContext.getMethodGen(method);
    if (methodGen == null) return;
    String fullMethodName = methodGen.getClassName() + "." + methodGen.getName();

    String sourceFile = classContext.getJavaClass().getSourceFileName();
    if (DEBUG) {
      System.out.println("\n" + fullMethodName);
    }

    // Process each instruction
    for (Iterator<Location> iter = cfg.locationIterator(); iter.hasNext(); ) {
      Location location = iter.next();
      InstructionHandle handle = location.getHandle();
      Instruction ins = handle.getInstruction();

      // Only consider invoke instructions
      if (!(ins instanceof InvokeInstruction)) continue;

      InvokeInstruction inv = (InvokeInstruction) ins;

      XMethod invokedMethod = XFactory.createXMethod(inv, cpg);

      String invokedMethodName = invokedMethod.getName();
      String argSignature = invokedMethod.getSignature();
      argSignature = argSignature.substring(0, argSignature.indexOf(')') + 1);
      String call = invokedMethodName + argSignature;
      SignatureParser sigParser = new SignatureParser(inv.getSignature(cpg));

      Collection<Info> collection = callMap.get(call);
      if (!callMap.containsKey(call)) continue;
      for (Info info : collection) {
        Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2();
        if (DEBUG)
          System.out.println(
              "at "
                  + handle.getPosition()
                  + " Checking call to "
                  + info.interfaceForCall
                  + " : "
                  + invokedMethod);
        try {
          if (!subtypes2.isSubtype(invokedMethod.getClassDescriptor(), info.interfaceForCall))
            continue;
        } catch (ClassNotFoundException e) {
          if (info.interfaceForCall.getClassName().equals("java/util/Collection")
              && invokedMethod.getClassName().equals("com.google.common.collect.Multiset")) {
            assert true;
            // we know this is OK without needing to find definition of Multiset
          } else {
            AnalysisContext.reportMissingClass(e);
            continue;
          }
        }

        boolean allMethod;

        int typeArgument;
        if (info.typeIndex >= 0) {
          allMethod = false;
          typeArgument = info.typeIndex;
        } else {
          allMethod = true;
          typeArgument = -(1 + info.typeIndex);
        }
        int pos = info.argumentIndex;

        int lhsPos;
        if (inv instanceof INVOKESTATIC) lhsPos = sigParser.getSlotsFromTopOfStackForParameter(0);
        else lhsPos = sigParser.getTotalArgumentSize();

        int stackPos = sigParser.getSlotsFromTopOfStackForParameter(pos);

        TypeFrame frame = typeDataflow.getFactAtLocation(location);
        if (!frame.isValid()) {
          // This basic block is probably dead
          continue;
        }

        Type operandType = frame.getStackValue(stackPos);
        if (operandType.equals(TopType.instance())) {
          // unreachable
          continue;
        }

        if (operandType.equals(NullType.instance())) {
          // ignore
          continue;
        }

        ValueNumberFrame vnFrame = vnDataflow.getFactAtLocation(location);

        if (!vnFrame.isValid()) {
          AnalysisContext.logError("Invalid value number frame in " + xmethod);
          continue;
        }

        ValueNumber objectVN = vnFrame.getStackValue(lhsPos);
        ValueNumber argVN = vnFrame.getStackValue(stackPos);

        if (objectVN.equals(argVN)) {
          String bugPattern = "DMI_COLLECTIONS_SHOULD_NOT_CONTAIN_THEMSELVES";
          int priority = HIGH_PRIORITY;
          if (invokedMethodName.equals("removeAll")) {
            bugPattern = "DMI_USING_REMOVEALL_TO_CLEAR_COLLECTION";
            priority = NORMAL_PRIORITY;
          } else if (invokedMethodName.endsWith("All")) {
            bugPattern = "DMI_VACUOUS_SELF_COLLECTION_CALL";
            priority = NORMAL_PRIORITY;
          }
          if (invokedMethodName.startsWith("contains")) {
            InstructionHandle next = handle.getNext();
            if (next != null) {
              Instruction nextIns = next.getInstruction();

              if (nextIns instanceof InvokeInstruction) {
                XMethod nextMethod = XFactory.createXMethod((InvokeInstruction) nextIns, cpg);
                if (nextMethod.getName().equals("assertFalse")) continue;
              }
            }
          }
          accumulator.accumulateBug(
              new BugInstance(this, bugPattern, priority)
                  .addClassAndMethod(methodGen, sourceFile)
                  .addCalledMethod(methodGen, (InvokeInstruction) ins)
                  .addOptionalAnnotation(
                      ValueNumberSourceInfo.findAnnotationFromValueNumber(
                          method, location, objectVN, vnFrame, "INVOKED_ON")),
              SourceLineAnnotation.fromVisitedInstruction(
                  classContext, methodGen, sourceFile, handle));
        }

        // Only consider generic...
        Type objectType = frame.getStackValue(lhsPos);
        if (!(objectType instanceof GenericObjectType)) continue;

        GenericObjectType operand = (GenericObjectType) objectType;

        int expectedTypeParameters = 1;
        String simpleName = info.interfaceForCall.getSimpleName();
        if (simpleName.toLowerCase().endsWith("map") || simpleName.equals("Hashtable"))
          expectedTypeParameters = 2;
        else if (simpleName.equals("Table")) expectedTypeParameters = 3;

        // ... containers
        if (!operand.hasParameters()) continue;
        if (operand.getNumParameters() != expectedTypeParameters) continue;
        ClassDescriptor operandClass = DescriptorFactory.getClassDescriptor(operand);
        if (!isGenericCollection(operandClass)) continue;

        if (expectedTypeParameters == 2
            && Subtypes2.instanceOf(operandClass, Map.class)
            && !TypeFrameModelingVisitor.isStraightGenericMap(operandClass)) continue;
        Type expectedType;
        if (allMethod) expectedType = operand;
        else expectedType = operand.getParameterAt(typeArgument);
        Type actualType = frame.getStackValue(stackPos);
        Type equalsType = actualType;
        if (allMethod) {
          if (!(actualType instanceof GenericObjectType)) {
            continue;
          }
          equalsType = ((GenericObjectType) actualType).getParameterAt(typeArgument);
        }

        IncompatibleTypes matchResult = compareTypes(expectedType, actualType, allMethod);

        boolean parmIsObject = expectedType.getSignature().equals("Ljava/lang/Object;");
        boolean selfOperation = !allMethod && operand.equals(actualType) && !parmIsObject;
        if (!allMethod && !parmIsObject && actualType instanceof GenericObjectType) {

          GenericObjectType p2 = (GenericObjectType) actualType;
          List<? extends ReferenceType> parameters = p2.getParameters();
          if (parameters != null && parameters.equals(operand.getParameters()))
            selfOperation = true;
        }

        if (!selfOperation
            && (matchResult == IncompatibleTypes.SEEMS_OK
                || matchResult.getPriority() == Priorities.IGNORE_PRIORITY)) continue;

        if (invokedMethodName.startsWith("contains") || invokedMethodName.equals("remove")) {
          InstructionHandle next = handle.getNext();
          if (next != null) {
            Instruction nextIns = next.getInstruction();

            if (nextIns instanceof InvokeInstruction) {
              XMethod nextMethod = XFactory.createXMethod((InvokeInstruction) nextIns, cpg);
              if (nextMethod.getName().equals("assertFalse")) continue;
            }
          }
        } else if (invokedMethodName.equals("get") || invokedMethodName.equals("remove")) {
          InstructionHandle next = handle.getNext();
          if (next != null) {
            Instruction nextIns = next.getInstruction();

            if (nextIns instanceof InvokeInstruction) {
              XMethod nextMethod = XFactory.createXMethod((InvokeInstruction) nextIns, cpg);
              if (nextMethod.getName().equals("assertNull")) continue;
            }
          }
        }
        boolean noisy = false;
        if (invokedMethodName.equals("get")) {
          UnconditionalValueDerefDataflow unconditionalValueDerefDataflow =
              classContext.getUnconditionalValueDerefDataflow(method);

          UnconditionalValueDerefSet unconditionalDeref =
              unconditionalValueDerefDataflow.getFactAtLocation(location);
          ValueNumberFrame vnAfter = vnDataflow.getFactAfterLocation(location);
          ValueNumber top = vnAfter.getTopValue();
          noisy =
              unconditionalDeref.getValueNumbersThatAreUnconditionallyDereferenced().contains(top);
        }
        // Prepare bug report
        SourceLineAnnotation sourceLineAnnotation =
            SourceLineAnnotation.fromVisitedInstruction(
                classContext, methodGen, sourceFile, handle);

        // Report a bug that mentions each of the failed arguments in
        // matches

        if (expectedType instanceof GenericObjectType)
          expectedType = ((GenericObjectType) expectedType).getUpperBound();

        int priority = matchResult.getPriority();
        if (!operandClass.getClassName().startsWith("java/util")
            && priority == Priorities.HIGH_PRIORITY)
          priority = Math.max(priority, Priorities.NORMAL_PRIORITY);
        if (TestCaseDetector.likelyTestCase(xmethod))
          priority = Math.max(priority, Priorities.NORMAL_PRIORITY);
        else if (selfOperation) priority = Priorities.HIGH_PRIORITY;
        ClassDescriptor expectedClassDescriptor =
            DescriptorFactory.createClassOrObjectDescriptorFromSignature(
                expectedType.getSignature());
        ClassDescriptor actualClassDescriptor =
            DescriptorFactory.createClassOrObjectDescriptorFromSignature(equalsType.getSignature());
        ClassSummary classSummary = AnalysisContext.currentAnalysisContext().getClassSummary();
        Set<XMethod> targets = null;
        try {
          targets =
              Hierarchy2.resolveVirtualMethodCallTargets(
                  actualClassDescriptor, "equals", "(Ljava/lang/Object;)Z", false, false);
          boolean allOk = targets.size() > 0;
          for (XMethod m2 : targets)
            if (!classSummary.mightBeEqualTo(m2.getClassDescriptor(), expectedClassDescriptor))
              allOk = false;
          if (allOk) priority += 2;
        } catch (ClassNotFoundException e) {
          AnalysisContext.reportMissingClass(e);
        }
        String bugPattern = "GC_UNRELATED_TYPES";

        BugInstance bug =
            new BugInstance(this, bugPattern, priority)
                .addClassAndMethod(methodGen, sourceFile)
                .addFoundAndExpectedType(actualType, expectedType)
                .addCalledMethod(methodGen, (InvokeInstruction) ins)
                .addOptionalAnnotation(
                    ValueNumberSourceInfo.findAnnotationFromValueNumber(
                        method, location, objectVN, vnFrame, "INVOKED_ON"))
                .addOptionalAnnotation(
                    ValueNumberSourceInfo.findAnnotationFromValueNumber(
                        method, location, argVN, vnFrame, "ARGUMENT"))
                .addEqualsMethodUsed(targets);
        if (noisy) {
          WarningPropertySet<WarningProperty> propertySet =
              new WarningPropertySet<WarningProperty>();

          propertySet.addProperty(GeneralWarningProperty.NOISY_BUG);
          propertySet.decorateBugInstance(bug);
        }
        accumulator.accumulateBug(bug, sourceLineAnnotation);
      }
    }
    accumulator.reportAccumulatedBugs();
  }

  /**
   * Compare to see if the argument <code>argType</code> passed to the method matches the type of
   * the corresponding parameter. The simplest case is when both are equal.
   *
   * <p>This is a conservative comparison - returns true if it cannot decide. If the parameter type
   * is a type variable (e.g. <code>T</code>) then we don't know enough (yet) to decide if they do
   * not match so return true.
   *
   * @param ignoreBaseType TODO
   */
  private IncompatibleTypes compareTypes(
      Type expectedType, Type actualType, boolean ignoreBaseType) {
    // XXX equality not implemented for GenericObjectType
    // if (parmType.equals(argType)) return true;
    if (expectedType == actualType) return IncompatibleTypes.SEEMS_OK;
    // Compare type signatures instead
    String expectedString = GenericUtilities.getString(expectedType);
    String actualString = GenericUtilities.getString(actualType);
    if (expectedString.equals(actualString)) return IncompatibleTypes.SEEMS_OK;

    if (expectedType.equals(Type.OBJECT)) return IncompatibleTypes.SEEMS_OK;
    // if either type is java.lang.Object, then automatically true!
    // again compare strings...

    String objString = GenericUtilities.getString(Type.OBJECT);

    if (expectedString.equals(objString)) {
      return IncompatibleTypes.SEEMS_OK;
    }

    // get a category for each type
    TypeCategory expectedCat = GenericUtilities.getTypeCategory(expectedType);
    TypeCategory argCat = GenericUtilities.getTypeCategory(actualType);
    if (actualString.equals(objString) && expectedCat == TypeCategory.TYPE_VARIABLE) {
      return IncompatibleTypes.SEEMS_OK;
    }
    if (ignoreBaseType) {
      if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) {
        GenericObjectType parmGeneric = (GenericObjectType) expectedType;
        GenericObjectType argGeneric = (GenericObjectType) actualType;
        return compareTypeParameters(parmGeneric, argGeneric);
      }
      return IncompatibleTypes.SEEMS_OK;
    }

    if (actualType.equals(Type.OBJECT) && expectedCat == TypeCategory.ARRAY_TYPE)
      return IncompatibleTypes.ARRAY_AND_OBJECT;

    // -~- plain objects are easy
    if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PLAIN_OBJECT_TYPE)
      return IncompatibleTypes.getPriorityForAssumingCompatible(expectedType, actualType, false);

    if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PLAIN_OBJECT_TYPE)
      return IncompatibleTypes.getPriorityForAssumingCompatible(
          (GenericObjectType) expectedType, actualType);
    if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PARAMETERIZED)
      return IncompatibleTypes.getPriorityForAssumingCompatible(
          (GenericObjectType) actualType, expectedType);

    // -~- parmType is: "? extends Another Type" OR "? super Another Type"
    if (expectedCat == TypeCategory.WILDCARD_EXTENDS || expectedCat == TypeCategory.WILDCARD_SUPER)
      return compareTypes(
          ((GenericObjectType) expectedType).getExtension(), actualType, ignoreBaseType);

    // -~- Not handling type variables
    if (expectedCat == TypeCategory.TYPE_VARIABLE || argCat == TypeCategory.TYPE_VARIABLE)
      return IncompatibleTypes.SEEMS_OK;

    // -~- Array Types: compare dimensions, then base type
    if (expectedCat == TypeCategory.ARRAY_TYPE && argCat == TypeCategory.ARRAY_TYPE) {
      ArrayType parmArray = (ArrayType) expectedType;
      ArrayType argArray = (ArrayType) actualType;

      if (parmArray.getDimensions() != argArray.getDimensions())
        return IncompatibleTypes.ARRAY_AND_NON_ARRAY;

      return compareTypes(parmArray.getBasicType(), argArray.getBasicType(), ignoreBaseType);
    }
    // If one is an Array Type and the other is not, then they
    // are incompatible. (We already know neither is java.lang.Object)
    if (expectedCat == TypeCategory.ARRAY_TYPE ^ argCat == TypeCategory.ARRAY_TYPE) {
      return IncompatibleTypes.ARRAY_AND_NON_ARRAY;
    }

    // -~- Parameter Types: compare base type then parameters
    if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) {
      GenericObjectType parmGeneric = (GenericObjectType) expectedType;
      GenericObjectType argGeneric = (GenericObjectType) actualType;

      // base types should be related
      {
        IncompatibleTypes result =
            compareTypes(parmGeneric.getObjectType(), argGeneric.getObjectType(), ignoreBaseType);
        if (!result.equals(IncompatibleTypes.SEEMS_OK)) return result;
      }
      return compareTypeParameters(parmGeneric, argGeneric);

      // XXX More to come
    }
    // If one is a Parameter Type and the other is not, then they
    // are incompatible. (We already know neither is java.lang.Object)
    if (false) {
      // not true. Consider class Foo extends ArrayList<String>
      if (expectedCat == TypeCategory.PARAMETERIZED ^ argCat == TypeCategory.PARAMETERIZED) {
        return IncompatibleTypes.SEEMS_OK; // fix this when we know what
        // we are doing here
      }
    }

    // -~- Wildcard e.g. List<*>.contains(...)
    if (expectedCat == TypeCategory.WILDCARD) // No Way to know
    return IncompatibleTypes.SEEMS_OK;

    // -~- Non Reference types
    // if ( parmCat == TypeCategory.NON_REFERENCE_TYPE ||
    // argCat == TypeCategory.NON_REFERENCE_TYPE )
    if (expectedType instanceof BasicType || actualType instanceof BasicType) {
      // this should not be possible, compiler will complain (pre 1.5)
      // or autobox primitive types (1.5 +)
      throw new IllegalArgumentException(
          "checking for compatibility of " + expectedType + " with " + actualType);
    }

    return IncompatibleTypes.SEEMS_OK;
  }

  private IncompatibleTypes compareTypeParameters(
      GenericObjectType parmGeneric, GenericObjectType argGeneric) {
    int p = parmGeneric.getNumParameters();
    if (p != argGeneric.getNumParameters()) {
      return IncompatibleTypes.SEEMS_OK;
    }
    for (int x = 0; x < p; x++) {
      IncompatibleTypes result =
          compareTypes(parmGeneric.getParameterAt(x), argGeneric.getParameterAt(x), false);
      if (result != IncompatibleTypes.SEEMS_OK) return result;
    }
    return IncompatibleTypes.SEEMS_OK;
  }

  // old version of compare types
  private boolean compareTypesOld(Type parmType, Type argType) {
    // XXX equality not implemented for GenericObjectType
    // if (parmType.equals(argType)) return true;
    // Compare type signatures instead
    if (GenericUtilities.getString(parmType).equals(GenericUtilities.getString(argType)))
      return true;

    if (parmType instanceof GenericObjectType) {
      GenericObjectType o = (GenericObjectType) parmType;
      if (o.getTypeCategory() == GenericUtilities.TypeCategory.WILDCARD_EXTENDS) {
        return compareTypesOld(o.getExtension(), argType);
      }
    }
    // ignore type variables for now
    if (parmType instanceof GenericObjectType && !((GenericObjectType) parmType).hasParameters())
      return true;
    if (argType instanceof GenericObjectType && !((GenericObjectType) argType).hasParameters())
      return true;

    // Case: Both are generic containers
    if (parmType instanceof GenericObjectType && argType instanceof GenericObjectType) {
      return true;
    } else {
      // Don't consider non reference types (should not be possible)
      if (!(parmType instanceof ReferenceType && argType instanceof ReferenceType)) return true;

      // Don't consider non object types (for now)
      if (!(parmType instanceof ObjectType && argType instanceof ObjectType)) return true;

      // Otherwise, compare base types ignoring generic information
      try {
        return Repository.instanceOf(
            ((ObjectType) argType).getClassName(), ((ObjectType) parmType).getClassName());
      } catch (ClassNotFoundException e) {
      }
    }

    return true;
  }

  /**
   * Empty
   *
   * @see edu.umd.cs.findbugs.Detector#report()
   */
  public void report() {}
}
/**
 * A TypeQualifierValue is a pair specifying a type qualifier annotation and a value. Each
 * TypeQualifierValue is effectively a different type qualifier. For example, if Foo is a type
 * qualifier annotation having an int value, then Foo(0), Foo(1), etc. are all different type
 * qualifiers which must be checked separately.
 *
 * @author William Pugh
 */
public class TypeQualifierValue<A extends Annotation> {
  public static final boolean DEBUG = SystemProperties.getBoolean("tqv.debug");

  private static final ClassDescriptor EXCLUSIVE_ANNOTATION =
      DescriptorFactory.instance().getClassDescriptor(javax.annotation.meta.Exclusive.class);

  private static final ClassDescriptor EXHAUSTIVE_ANNOTATION =
      DescriptorFactory.instance().getClassDescriptor(javax.annotation.meta.Exhaustive.class);

  public final ClassDescriptor typeQualifier;
  public final Class<A> typeQualifierClass;

  public final A proxy;

  public final @CheckForNull Object value;

  private final boolean isStrict;

  private final boolean isExclusive;

  private final boolean isExhaustive;

  private final @CheckForNull TypeQualifierValidator<A> validator;

  private static final ClassLoader validatorLoader = new ValidatorClassLoader();

  private TypeQualifierValue(ClassDescriptor typeQualifier, @CheckForNull Object value) {
    this.typeQualifier = typeQualifier;
    this.value = value;
    boolean isStrict = false; // will be set to true if this is a strict
    // type qualifier value
    boolean isExclusive = false; // will be set to true if this is an
    // exclusive type qualifier value
    boolean isExhaustive = false; // will be set to true if this is an
    // exhaustive type qualifier value

    TypeQualifierValidator<A> validator = null;
    Class<A> qualifierClass = null;
    A proxy = null;
    try {
      XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, typeQualifier);

      // Annotation elements appear as abstract methods in the annotation
      // class (interface).
      // So, if the type qualifier annotation has specified a default When
      // value,
      // it will appear as an abstract method called "when".
      XMethod whenMethod = xclass.findMethod("when", "()Ljavax/annotation/meta/When;", false);
      if (whenMethod == null) {
        isStrict = true;
      }
      for (XMethod xmethod : xclass.getXMethods()) {
        if (xmethod.getName().equals("value") && xmethod.getSignature().startsWith("()")) {
          isExhaustive = xmethod.getAnnotation(EXHAUSTIVE_ANNOTATION) != null;
          if (isExhaustive) {
            // exhaustive qualifiers are automatically exclusive
            isExclusive = true;
          } else {
            // see if there is an explicit @Exclusive annotation
            isExclusive = xmethod.getAnnotation(EXCLUSIVE_ANNOTATION) != null;
          }

          break;
        }
      }
    } catch (MissingClassException e) {
      AnalysisContext.currentAnalysisContext()
          .getLookupFailureCallback()
          .reportMissingClass(e.getClassNotFoundException());
    } catch (CheckedAnalysisException e) {
      AnalysisContext.logError(
          "Error looking up annotation class " + typeQualifier.toDottedClassName(), e);
    }
    this.isStrict = isStrict;
    this.isExclusive = isExclusive;
    this.isExhaustive = isExhaustive;
    ClassDescriptor checkerName =
        DescriptorFactory.createClassDescriptor(typeQualifier.getClassName() + "$Checker");
    try {
      Global.getAnalysisCache().getClassAnalysis(ClassData.class, checkerName);
      // found it.
      //            System.out.println(checkerName);
      SecurityManager m = System.getSecurityManager();
      if (m == null) System.setSecurityManager(new ValidationSecurityManager());
      Class<?> c = validatorLoader.loadClass(checkerName.getDottedClassName());
      if (TypeQualifierValidator.class.isAssignableFrom(c)) {
        Class<? extends TypeQualifierValidator> checkerClass =
            c.asSubclass(TypeQualifierValidator.class);
        validator = getValidator(checkerClass);
        qualifierClass = getQualifierClass(typeQualifier);

        InvocationHandler handler =
            new InvocationHandler() {

              public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
                if (arg1.getName() == "value") return TypeQualifierValue.this.value;
                throw new UnsupportedOperationException("Can't handle " + arg1);
              }
            };

        proxy =
            qualifierClass.cast(
                Proxy.newProxyInstance(validatorLoader, new Class[] {qualifierClass}, handler));
      }
    } catch (ClassNotFoundException e) {
      assert true; // ignore
    } catch (CheckedAnalysisException e) {
      assert true; // ignore
    } catch (Exception e) {
      AnalysisContext.logError("Unable to construct type qualifier checker " + checkerName, e);
    } catch (Throwable e) {
      AnalysisContext.logError(
          "Unable to construct type qualifier checker "
              + checkerName
              + " due to "
              + e.getClass().getSimpleName()
              + ":"
              + e.getMessage());
    }
    this.validator = validator;
    this.typeQualifierClass = qualifierClass;
    this.proxy = proxy;
  }

  /**
   * @param checkerClass
   * @return
   * @throws InstantiationException
   * @throws IllegalAccessException
   */
  @SuppressWarnings("unchecked")
  private TypeQualifierValidator<A> getValidator(
      Class<? extends TypeQualifierValidator> checkerClass)
      throws InstantiationException, IllegalAccessException {
    return checkerClass.newInstance();
  }

  /**
   * @param typeQualifier
   * @return
   * @throws ClassNotFoundException
   */
  @SuppressWarnings("unchecked")
  private Class<A> getQualifierClass(ClassDescriptor typeQualifier) throws ClassNotFoundException {
    return (Class<A>) validatorLoader.loadClass(typeQualifier.getDottedClassName());
  }

  static class Data {
    /** Cache in which constructed TypeQualifierValues are interned. */
    DualKeyHashMap<ClassDescriptor, Object, TypeQualifierValue<?>> typeQualifierMap =
        new DualKeyHashMap<ClassDescriptor, Object, TypeQualifierValue<?>>();

    /** Set of all known TypeQualifierValues. */
    Set<TypeQualifierValue<?>> allKnownTypeQualifiers = new HashSet<TypeQualifierValue<?>>();
  }

  private static ThreadLocal<Data> instance =
      new ThreadLocal<Data>() {
        @Override
        protected Data initialValue() {
          return new Data();
        }
      };

  public static void clearInstance() {
    instance.remove();
  }

  public boolean canValidate(Object constantValue) {
    if (validator == null) return false;
    return true;
  }

  private static final InheritableThreadLocal<AtomicBoolean> performingValidation =
      new InheritableThreadLocal<AtomicBoolean>() {
        @Override
        protected AtomicBoolean initialValue() {
          return new AtomicBoolean();
        }
      };

  static final class ValidationSecurityManager extends SecurityManager {
    @Override
    public void checkPermission(Permission perm) {
      //            System.out.println("Checking " + perm);
      if (performingValidation.get().get())
        throw new SecurityException("not permissions granted while performing JSR-305 validation");
    }

    @Override
    public void checkPermission(Permission perm, Object context) {
      if (performingValidation.get().get())
        throw new SecurityException("not permissions granted while performing JSR-305 validation");
    }
  }

  public When validate(Object constantValue) {
    if (validator == null) throw new IllegalStateException("No validator");
    IAnalysisCache analysisCache = Global.getAnalysisCache();
    Profiler profiler = analysisCache.getProfiler();
    profiler.start(validator.getClass());
    AtomicBoolean performing = performingValidation.get();
    try {
      if (!performing.compareAndSet(false, true)) {
        throw new IllegalStateException("recursive validation");
      }

      return validator.forConstantValue(proxy, constantValue);
    } catch (Exception e) {
      AnalysisContext.logError(
          "Error executing custom validator for " + typeQualifier + " " + constantValue, e);
      return When.UNKNOWN;
    } finally {
      if (!performing.compareAndSet(true, false)) {
        throw new IllegalStateException("performingValidation not set when validation completes");
      }
      profiler.end(validator.getClass());
    }
  }

  /**
   * Given a ClassDescriptor/value pair, return the interned TypeQualifierValue representing that
   * pair.
   *
   * @param desc a ClassDescriptor denoting a type qualifier annotation
   * @param value a value
   * @return an interned TypeQualifierValue object
   */
  public static @Nonnull TypeQualifierValue<?> getValue(ClassDescriptor desc, Object value) {
    DualKeyHashMap<ClassDescriptor, Object, TypeQualifierValue<?>> map =
        instance.get().typeQualifierMap;
    TypeQualifierValue<?> result = map.get(desc, value);
    if (result != null) return result;
    result = new TypeQualifierValue(desc, value);
    map.put(desc, value, result);
    instance.get().allKnownTypeQualifiers.add(result);
    return result;
  }

  @SuppressWarnings("unchecked")
  public static @Nonnull <A extends Annotation> TypeQualifierValue<A> getValue(
      Class<A> clazz, Object value) {
    return (TypeQualifierValue<A>) getValue(DescriptorFactory.createClassDescriptor(clazz), value);
  }

  /**
   * Get Collection of all known TypeQualifierValues.
   *
   * @return Collection of all known TypeQualifierValues
   */
  public static Collection<TypeQualifierValue<?>> getAllKnownTypeQualifiers() {
    return Collections.unmodifiableSet(instance.get().allKnownTypeQualifiers);
  }

  /**
   * Get the "complementary" TypeQualifierValues for given exclusive type qualifier.
   *
   * @param tqv a type qualifier (which must be exclusive)
   * @return Collection of complementary exclusive type qualifiers
   */
  public static Collection<TypeQualifierValue> getComplementaryExclusiveTypeQualifierValue(
      TypeQualifierValue tqv) {
    assert tqv.isExclusiveQualifier();

    LinkedList<TypeQualifierValue> result = new LinkedList<TypeQualifierValue>();

    for (TypeQualifierValue t : instance.get().allKnownTypeQualifiers) {
      //
      // Any TypeQualifierValue with the same
      // annotation class but a different value is a complementary
      // type qualifier.
      //
      if (t.typeQualifier.equals(tqv.typeQualifier) && !Util.nullSafeEquals(t.value, tqv.value)) {
        result.add(t);
      }
    }

    return result;
  }

  /**
   * Determine whether or not given TypeQualifierValue has multiple variants. I.e., if Color is a
   * type qualifier having values RED, GREEN, and BLUE, then there are 3 variants, Color(RED),
   * Color(GREEN), and COLOR(BLUE).
   *
   * @param tqv a TypeQualifierValue
   * @return true if there are multiple variants of this type qualifier, false otherwise
   */
  public static boolean hasMultipleVariants(TypeQualifierValue tqv) {
    int count = 0;
    for (TypeQualifierValue t : instance.get().allKnownTypeQualifiers) {
      if (t.typeQualifier.equals(tqv.typeQualifier)) {
        count++;
      }
    }
    return count > 1;
  }

  /**
   * Get the ClassDescriptor which specifies the type qualifier annotation.
   *
   * @return ClassDescriptor which specifies the type qualifier annotation
   */
  public ClassDescriptor getTypeQualifierClassDescriptor() {
    return typeQualifier;
  }

  /**
   * Return whether or not this TypeQualifierValue denotes a strict qualifier.
   *
   * @return true if type qualifier is strict, false otherwise
   */
  public boolean isStrictQualifier() {
    return isStrict;
  }

  /**
   * Return whether or not this TypeQualifierValue denotes an exclusive qualifier.
   *
   * @return true if type qualifier is exclusive, false otherwise
   */
  public boolean isExclusiveQualifier() {
    return isExclusive;
  }

  /**
   * Return whether or not this TypeQualifierValue denotes an exhaustive qualifier.
   *
   * @return true if type qualifier is exhaustive, false otherwise
   */
  public boolean isExhaustiveQualifier() {
    return isExhaustive;
  }

  @Override
  public int hashCode() {
    int result = typeQualifier.hashCode();
    if (value != null) result += 37 * value.hashCode();
    return result;
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof TypeQualifierValue)) return false;
    TypeQualifierValue other = (TypeQualifierValue) o;
    return typeQualifier.equals(other.typeQualifier) && Util.nullSafeEquals(value, other.value);
  }

  /*
   * (non-Javadoc)
   *
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder();
    buf.append(typeQualifier.toString());
    if (value != null) {
      buf.append(':');
      buf.append(value.toString());
    }
    return buf.toString();
  }
}