/**
   * looks to see if this class (or some class in its hierarchy (besides Object) has implemented the
   * specified method.
   *
   * @param cls the class to look in
   * @param methodName the method name to look for
   * @param methodSig the method signature to look for
   * @return when toString is found
   * @throws ClassNotFoundException if a super class can't be found
   */
  private static boolean hasMethodInHierarchy(JavaClass cls, String methodName, String methodSig)
      throws ClassNotFoundException {
    MethodInfo mi = null;

    do {
      String clsName = cls.getClassName();
      if (Values.DOTTED_JAVA_LANG_OBJECT.equals(clsName)) {
        return false;
      }

      mi =
          Statistics.getStatistics()
              .getMethodStatistics(clsName.replace('.', '/'), methodName, methodSig);
      cls = cls.getSuperClass();
    } while (mi.getNumBytes() == 0);

    return true;
  }
  /**
   * overrides the visitor to look for calls to static methods that are known to return immutable
   * collections It records those variables, and documents if what the method returns is one of
   * those objects.
   */
  @Override
  public void sawOpcode(int seen) {
    ImmutabilityType seenImmutable = null;
    try {
      stack.precomputation(this);

      switch (seen) {
        case INVOKESTATIC:
          {
            String className = getClassConstantOperand();
            String methodName = getNameConstantOperand();

            if (IMMUTABLE_PRODUCING_METHODS.contains(className + '.' + methodName)) {
              seenImmutable = ImmutabilityType.IMMUTABLE;
              break;
            }
          }
          // $FALL-THROUGH$
        case INVOKEINTERFACE:
        case INVOKESPECIAL:
        case INVOKEVIRTUAL:
          {
            String className = getClassConstantOperand();
            String methodName = getNameConstantOperand();
            String signature = getSigConstantOperand();

            MethodInfo mi =
                Statistics.getStatistics().getMethodStatistics(className, methodName, signature);
            seenImmutable = mi.getImmutabilityType();
            if (seenImmutable == ImmutabilityType.UNKNOWN) seenImmutable = null;
          }
          break;

        case ARETURN:
          {
            if (stack.getStackDepth() > 0) {
              OpcodeStack.Item item = stack.getStackItem(0);
              ImmutabilityType type = (ImmutabilityType) item.getUserValue();
              if (type == null) type = ImmutabilityType.UNKNOWN;

              switch (imType) {
                case UNKNOWN:
                  switch (type) {
                    case IMMUTABLE:
                      imType = ImmutabilityType.IMMUTABLE;
                      break;
                    case POSSIBLY_IMMUTABLE:
                      imType = ImmutabilityType.POSSIBLY_IMMUTABLE;
                      break;
                    default:
                      imType = ImmutabilityType.MUTABLE;
                      break;
                  }
                  break;

                case IMMUTABLE:
                  if (type != ImmutabilityType.IMMUTABLE) {
                    imType = ImmutabilityType.POSSIBLY_IMMUTABLE;
                  }
                  break;

                case POSSIBLY_IMMUTABLE:
                  break;

                case MUTABLE:
                  if (type == ImmutabilityType.IMMUTABLE) {
                    imType = ImmutabilityType.POSSIBLY_IMMUTABLE;
                  }
                  break;
              }
            }
            break;
          }
        default:
          break;
      }

    } finally {
      stack.sawOpcode(this, seen);
      if (seenImmutable != null) {
        if (stack.getStackDepth() > 0) {
          OpcodeStack.Item item = stack.getStackItem(0);
          item.setUserValue(seenImmutable);
        }
      }
    }
  }