/**
   * 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);
  }
 /**
  * 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);
 }
/**
 * 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;
  }
}
  private Pattern visitPatternDescr(PatternDescr descr, VerifierComponent parent, int orderNumber)
      throws UnknownDescriptionException {

    objectType = data.getObjectTypeByFullName(descr.getObjectType());

    if (objectType == null) {
      Import objectImport = data.getImportByName(descr.getObjectType());

      if (objectImport != null) {
        objectType = ObjectTypeFactory.createObjectType(objectImport);
      } else {
        objectType = ObjectTypeFactory.createObjectType(descr.getObjectType());
      }

      data.add(objectType);
    }

    pattern = new Pattern(rule);
    if (parent != null) {
      pattern.setParentPath(parent.getPath());
      pattern.setParentType(parent.getVerifierComponentType());
    }
    pattern.setObjectTypePath(objectType.getPath());
    pattern.setName(objectType.getName());
    pattern.setPatternNot(solvers.getRuleSolver().isChildNot());
    pattern.setPatternExists(solvers.getRuleSolver().isExists());
    pattern.setPatternForall(solvers.getRuleSolver().isForall());
    pattern.setOrderNumber(orderNumber);

    if (descr.getIdentifier() != null) {
      Variable variable = new Variable(rule);
      variable.setName(descr.getIdentifier());

      variable.setObjectTypeType(VerifierComponentType.OBJECT_TYPE.getType());
      variable.setObjectTypePath(objectType.getPath());
      variable.setObjectTypeType(descr.getObjectType());

      data.add(variable);
    }

    // visit source.
    if (descr.getSource() != null) {
      visit(descr.getSource());
    } else {
      if (workingMemory == null) {
        workingMemory = new WorkingMemory();
        data.add(workingMemory);
      }
      pattern.setSourcePath(workingMemory.getPath());
      pattern.setSourceType(workingMemory.getVerifierComponentType());
    }

    solvers.startPatternSolver(pattern);

    visit(descr.getConstraint());

    solvers.endPatternSolver();

    data.add(pattern);

    return pattern;
  }