docs: public static Set<JavaClassAndMethod> resolveMethodCallTargets(ReferenceType receiverType,
              InvokeInstruction invokeInstruction, ConstantPoolGen cpg, boolean receiverTypeIsExact) throws ClassNotFoundException {
          HashSet<JavaClassAndMethod> result = new HashSet<>();
  
          if (invokeInstruction.getOpcode() == Const.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(Values.DOTTED_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() == Const.INVOKEVIRTUAL || invokeInstruction.getOpcode() == Const.INVOKEINTERFACE)
                  && (upperBound == null || !upperBound.getJavaClass().isFinal() && !upperBound.getMethod().isFinal())
                  && !receiverTypeIsExact;
  
          if (virtualCall) {
              if (!Values.DOTTED_JAVA_LANG_OBJECT.equals(receiverClassName)) {
  
                  // 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() & Const.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;
      }