@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); } }