@Override
 public BasicValue merge(BasicValue v, BasicValue w) {
   // values are not equal
   // BasicValue's equals method is too general
   if ((!isTagged(v) && !w.equals(v)) || (isTagged(v) && !v.equals(w))) {
     // w is a BasicValue
     if (isTagged(v) && !isTagged(w)) {
       return new TaggedValue(w.getType());
     }
     // v is a BasicValue or uninteresting and w is a interesting TaggedValue
     else if ((!isTagged(v) || tagged(v).canNotContainInput())
         && isTagged(w)
         && tagged(w).canContainInput()) {
       final TaggedValue taggedW = tagged(w);
       if (hasImportantDependencies(taggedW) && rightMergePriority) {
         // w has a merge priority, its grouped inputs will be returned
         final TaggedValue returnValue = removeUngroupedInputs(taggedW.copy());
         if (returnValue != null) {
           return returnValue;
         } else {
           return new TaggedValue(v.getType());
         }
       }
       return new TaggedValue(v.getType());
     }
     // v is a BasicValue and w is a uninteresting TaggedValue
     else if (!isTagged(v) && isTagged(w) && tagged(w).canNotContainInput()) {
       return v;
     }
     // merge v and w (both TaggedValues), v is interesting
     else if (isTagged(v) && isTagged(w) && tagged(v).canContainInput()) {
       final List<TaggedValue> list = Arrays.asList(tagged(v), tagged(w));
       final TaggedValue returnValue = mergeReturnValues(list);
       if (returnValue != null) {
         return returnValue;
       }
     }
     // v is a TaggedValue and uninteresting
     else if (isTagged(v) && tagged(v).canNotContainInput()) {
       return v;
     }
     // v and w are BasicValues and not equal
     return BasicValue.UNINITIALIZED_VALUE;
   }
   // v and w are equal, return one of them
   return v;
 }
  @Override
  public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2)
      throws AnalyzerException {
    switch (insn.getOpcode()) {
      case PUTFIELD: // put value2 into value1
        // skip untagged values
        if (!isTagged(value1)) {
          return null;
        }

        final TaggedValue taggedValue = (TaggedValue) value1;
        final FieldInsnNode field = (FieldInsnNode) insn;
        final boolean value2HasInputDependency = hasImportantDependencies(value2);

        // if value1 is not an input, make value1 a container and add value2 to it
        // PUTFIELD on inputs is not allowed
        if (!taggedValue.isInput() && value2HasInputDependency) {
          if (!taggedValue.canContainFields()) {
            taggedValue.setTag(Tag.CONTAINER);
          }
          taggedValue.addContainerMapping(field.name, tagged(value2), currentFrame);
        }
        // if value1 is filled with non-input, make it container and mark the field
        // PUTFIELD on inputs is not allowed
        else if (!taggedValue.isInput() && !value2HasInputDependency) {
          if (!taggedValue.canContainFields()) {
            taggedValue.setTag(Tag.CONTAINER);
          }
          taggedValue.addContainerMapping(field.name, null, currentFrame);
        }
        // PUTFIELD on input leads to input modification
        // make input regular
        else if (taggedValue.isInput()) {
          taggedValue.makeRegular();
        }
        return null;
      default:
        return super.binaryOperation(insn, value1, value2);
    }
  }
  @SuppressWarnings({"rawtypes", "unchecked"})
  @Override
  public BasicValue naryOperation(AbstractInsnNode insn, List rawValues) throws AnalyzerException {
    final List<BasicValue> values = (List<BasicValue>) rawValues;
    boolean isStatic = false;
    switch (insn.getOpcode()) {
      case INVOKESTATIC:
        isStatic = true;
      case INVOKESPECIAL:
      case INVOKEVIRTUAL:
      case INVOKEINTERFACE:
        final MethodInsnNode method = (MethodInsnNode) insn;
        String methodOwner = method.owner;

        // special case: in case that class is extending tuple we need to find the class
        // that contains the actual implementation to determine the tuple size
        if (method.name.equals("getField") || method.name.equals("setField")) {
          try {
            final String newMethodOwner =
                (String) findMethodNode(methodOwner, method.name, method.desc)[1];
            if (newMethodOwner.startsWith("org/apache/flink/api/java/tuple/Tuple")) {
              methodOwner = newMethodOwner;
            }
          } catch (IllegalStateException e) {
            // proceed with the known method owner
          }
        }

        // special case: collect method of Collector
        if (method.name.equals("collect")
            && methodOwner.equals("org/apache/flink/util/Collector")
            && isTagged(values.get(0))
            && tagged(values.get(0)).isCollector()) {
          // check for invalid return value
          if (isTagged(values.get(1)) && tagged(values.get(1)).isNull()) {
            analyzer.handleNullReturn();
          }
          // valid return value with input dependencies
          else if (hasImportantDependencies(values.get(1))) {
            // add a copy and a reference
            // to capture the current state and future changes in alternative paths
            analyzer.getCollectorValues().add(tagged(values.get(1)));
            analyzer.getCollectorValues().add(tagged(values.get(1)).copy());
          }
          // valid return value without input dependencies
          else {
            analyzer.getCollectorValues().add(null);
          }
        }
        // special case: iterator method of Iterable
        else if (method.name.equals("iterator")
            && methodOwner.equals("java/lang/Iterable")
            && isTagged(values.get(0))
            && tagged(values.get(0)).isInputIterable()) {
          return new TaggedValue(
              Type.getObjectType("java/util/Iterator"),
              (tagged(values.get(0)).isInput1Iterable())
                  ? Tag.INPUT_1_ITERATOR
                  : Tag.INPUT_2_ITERATOR);
        }
        // special case: hasNext method of Iterator
        else if (method.name.equals("hasNext")
            && methodOwner.equals("java/util/Iterator")
            && isTagged(values.get(0))
            && tagged(values.get(0)).isInputIterator()
            && !analyzer.isUdfBinary()
            && !analyzer.isIteratorTrueAssumptionApplied()) {
          return new TaggedValue(Type.BOOLEAN_TYPE, Tag.ITERATOR_TRUE_ASSUMPTION);
        }
        // special case: next method of Iterator
        else if (method.name.equals("next")
            && methodOwner.equals("java/util/Iterator")
            && isTagged(values.get(0))
            && tagged(values.get(0)).isInputIterator()) {
          // after this call it is not possible to assume "TRUE" of "hasNext()" again
          analyzer.applyIteratorTrueAssumption();
          if (tagged(values.get(0)).isInput1Iterator()) {
            return analyzer.getInput1AsTaggedValue();
          } else {
            return analyzer.getInput2AsTaggedValue();
          }
        }
        // if the UDF class contains instance variables that contain input,
        // we need to analyze also methods without input-dependent arguments
        // special case: do not follow the getRuntimeContext method of RichFunctions
        else if (!isStatic
            && isTagged(values.get(0))
            && tagged(values.get(0)).isThis()
            && hasImportantDependencies(values.get(0))
            && !isGetRuntimeContext(method)) {
          TaggedValue tv = invokeNestedMethod(values, method);
          if (tv != null) {
            return tv;
          }
        }
        // the arguments have input dependencies ("THIS" does not count to the arguments)
        // we can assume that method has at least one argument
        else if ((!isStatic
                && isTagged(values.get(0))
                && tagged(values.get(0)).isThis()
                && hasImportantDependencies(values, true))
            || (!isStatic
                && (!isTagged(values.get(0)) || !tagged(values.get(0)).isThis())
                && hasImportantDependencies(values, false))
            || (isStatic && hasImportantDependencies(values, false))) {
          // special case: Java unboxing/boxing methods on input
          Type newType;
          if (isTagged(values.get(0))
              && tagged(values.get(0)).isInput()
              && (!isStatic && (newType = checkForUnboxing(method.name, methodOwner)) != null
                  || (isStatic
                      && (newType = checkForBoxing(method.name, method.desc, methodOwner))
                          != null))) {
            return tagged(values.get(0)).copy(newType);
          }
          // special case: setField method of TupleXX
          else if (method.name.equals("setField")
              && methodOwner.startsWith("org/apache/flink/api/java/tuple/Tuple")
              && isTagged(values.get(0))) {
            final TaggedValue tuple = tagged(values.get(0));
            tuple.setTag(Tag.CONTAINER);

            // check if fieldPos is constant
            // if not, we can not determine a state for the tuple
            if (!isTagged(values.get(2)) || !tagged(values.get(2)).isIntConstant()) {
              tuple.makeRegular();
            } else {
              final int constant = tagged(values.get(2)).getIntConstant();

              if (constant < 0 || Integer.valueOf(methodOwner.split("Tuple")[1]) <= constant) {
                analyzer.handleInvalidTupleAccess();
              }

              // if it is at least tagged, add it anyways
              if (isTagged(values.get(1))) {
                tuple.addContainerMapping("f" + constant, tagged(values.get(1)), currentFrame);
              }
              // mark the field as it has an undefined state
              else {
                tuple.addContainerMapping("f" + constant, null, currentFrame);
              }
            }
          }
          // special case: getField method of TupleXX
          else if (method.name.equals("getField")
              && methodOwner.startsWith("org/apache/flink/api/java/tuple/Tuple")) {
            final TaggedValue tuple =
                tagged(values.get(0)); // we can assume that 0 is an input dependent tuple
            // constant field index
            if (isTagged(values.get(1)) // constant
                && tagged(values.get(1)).isIntConstant()) {
              final int constant = tagged(values.get(1)).getIntConstant();

              if (constant < 0 || Integer.valueOf(methodOwner.split("Tuple")[1]) <= constant) {
                analyzer.handleInvalidTupleAccess();
              }

              if (tuple.containerContains("f" + constant)) {
                final TaggedValue tupleField = tuple.getContainerMapping().get("f" + constant);
                if (tupleField != null) {
                  return tupleField;
                }
              }
            }
            // unknown field index
            else {
              // we need to make the tuple regular as we cannot track modifications of fields
              tuple.makeRegular();
              return new TaggedValue(Type.getObjectType("java/lang/Object"));
            }
          }
          // nested method invocation
          else {
            TaggedValue tv = invokeNestedMethod(values, method);
            if (tv != null) {
              return tv;
            }
          }
        }
        return super.naryOperation(insn, values);
      default:
        // TODO support for INVOKEDYNAMIC instructions
        return super.naryOperation(insn, values);
    }
  }
  @Override
  public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value)
      throws AnalyzerException {
    switch (insn.getOpcode()) {
        // modify jump instructions if we can assume that the hasNext operation will always
        // be true at the first call
      case IFEQ:
        if (isTagged(value) && tagged(value).isIteratorTrueAssumption()) {
          modifiedAsmAnalyzer.requestIFEQLoopModification();
        }
        return super.unaryOperation(insn, value);
      case IFNE:
        if (isTagged(value) && tagged(value).isIteratorTrueAssumption()) {
          modifiedAsmAnalyzer.requestIFNELoopModification();
        }
        return super.unaryOperation(insn, value);

      case CHECKCAST:
        return value;
      case PUTSTATIC:
        analyzer.handlePutStatic();
        return super.unaryOperation(insn, value);
      case GETFIELD:
        final FieldInsnNode field = (FieldInsnNode) insn;
        // skip untagged values
        if (!isTagged(value)) {
          return super.unaryOperation(insn, value);
        }
        final TaggedValue taggedValue = (TaggedValue) value;

        // inputs are atomic, a GETFIELD results in undefined state
        if (taggedValue.isInput()) {
          return super.unaryOperation(insn, value);
        }
        // access of input container field
        // or access of a KNOWN UDF instance variable
        else if (taggedValue.canContainFields() && taggedValue.containerContains(field.name)) {
          final TaggedValue tv = taggedValue.getContainerMapping().get(field.name);
          if (tv != null) {
            return tv;
          }
        }
        // access of a yet UNKNOWN UDF instance variable
        else if (taggedValue.isThis() && !taggedValue.containerContains(field.name)) {
          final TaggedValue tv = new TaggedValue(Type.getType(field.desc));
          taggedValue.addContainerMapping(field.name, tv, currentFrame);
          return tv;
        }
        // access of a yet unknown container, mark it as a container
        else if (taggedValue.isRegular()) {
          taggedValue.setTag(Tag.CONTAINER);
          final TaggedValue tv = new TaggedValue(Type.getType(field.desc));
          taggedValue.addContainerMapping(field.name, tv, currentFrame);
          return tv;
        }
        return super.unaryOperation(insn, value);
      case IINC:
        // modification of a local variable or input
        if (isTagged(value) && (tagged(value).isIntConstant() || tagged(value).isInput())) {
          tagged(value).makeRegular();
        }
        return super.unaryOperation(insn, value);
      default:
        return super.unaryOperation(insn, value);
    }
  }