All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.api.java.sca.NestedMethodAnalyzer Maven / Gradle / Ivy

There is a newer version: 1.20.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.flink.api.java.sca;

import static org.apache.flink.api.java.sca.UdfAnalyzerUtils.findMethodNode;
import static org.apache.flink.api.java.sca.UdfAnalyzerUtils.hasImportantDependencies;
import static org.apache.flink.api.java.sca.UdfAnalyzerUtils.isTagged;
import static org.apache.flink.api.java.sca.UdfAnalyzerUtils.mergeReturnValues;
import static org.apache.flink.api.java.sca.UdfAnalyzerUtils.removeUngroupedInputs;
import static org.apache.flink.api.java.sca.UdfAnalyzerUtils.tagged;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.flink.annotation.Internal;
import org.apache.flink.api.java.sca.TaggedValue.Tag;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.BasicValue;

/**
 * Extends ASM's BasicInterpreter. Instead of ASM's BasicValues, it introduces
 * TaggedValues which extend BasicValue and allows for appending interesting
 * information to values. The NestedMethodAnalyzer follows interesting values
 * and analyzes nested methods (control/method flow) if necessary.
 */
@Internal
public class NestedMethodAnalyzer extends BasicInterpreter {

	// reference to current UDF analysis context
	private final UdfAnalyzer analyzer;

	// flag that indicates if current method is still the Function's SAM method
	private final boolean topLevelMethod;

	// method description and arguments
	private final String owner;
	private final MethodNode methodNode;
	private final List argumentValues;

	// remaining nesting level
	private final int remainingNesting;

	// ASM analyzer which analyzes this methodNode
	private ModifiedASMAnalyzer modifiedAsmAnalyzer;

	public NestedMethodAnalyzer(UdfAnalyzer analyzer, String owner, MethodNode methodNode,
			List argumentValues, int remainingNesting, boolean topLevelMethod) {
		this.analyzer = analyzer;
		this.topLevelMethod = topLevelMethod;
		this.owner = owner;
		this.methodNode = methodNode;
		this.argumentValues = argumentValues;
		
		this.remainingNesting = remainingNesting;
		if (remainingNesting < 0) {
			throw new CodeAnalyzerException("Maximum nesting level reached.");
		}
	}

	public TaggedValue analyze() throws AnalyzerException {
		modifiedAsmAnalyzer = new ModifiedASMAnalyzer(this);

		// FOR DEBUGGING
		//		final Printer printer = new Textifier();
		//		final TraceMethodVisitor mp = new TraceMethodVisitor(printer);
		//		System.out.println(methodNode.name + " " + methodNode.desc);
		//		Iterator it = methodNode.instructions.iterator();
		//		while (it.hasNext()) {
		//			it.next().accept(mp);
		//			StringWriter sw = new StringWriter();
		//			printer.print(new PrintWriter(sw));
		//			printer.getText().clear();
		//			System.out.println(sw.toString());
		//		}
		modifiedAsmAnalyzer.analyze(owner, methodNode);
		return mergeReturnValues(returnValues);
	}

	@SuppressWarnings("unchecked")
	private TaggedValue invokeNestedMethod(List values,
			final MethodInsnNode methodInsn) throws AnalyzerException {
		final Object[] mn = findMethodNode(methodInsn.owner, methodInsn.name, methodInsn.desc);
		MethodNode methodNode = (MethodNode) mn[0];
		// recursion
		if (methodNode.name.equals(this.methodNode.name)
				&& methodNode.desc.equals(this.methodNode.desc)) {
			// TODO recursion are not supported perfectly yet
			// recursion only work if the nested call follows at least one return statement
			// return the values that are present so far
			return mergeReturnValues(returnValues);
		}
		final NestedMethodAnalyzer nma = new NestedMethodAnalyzer(analyzer, (String) mn[1],
				(MethodNode) mn[0],
				(List) values, remainingNesting -1,
				topLevelMethod && isBridgeMethod());
		return nma.analyze();
	}

	private boolean isBridgeMethod() {
		return (methodNode.access & ACC_BRIDGE) == ACC_BRIDGE;
	}

	private boolean isGetRuntimeContext(MethodInsnNode methodInsnNode) {
		return methodInsnNode.name.equals("getRuntimeContext")
				&& findMethodNode(methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc)[1]
				.equals("org/apache/flink/api/common/functions/AbstractRichFunction");
	}

	private Type checkForUnboxing(String name, String methodOwner) {
		// for performance improvement
		if (!methodOwner.startsWith("java/lang/")
				|| !name.endsWith("Value")) {
			return null;
		}

		final String actualType = methodOwner.substring(10);
		final String convertedType = name.substring(0, name.length() - 5);

		if (convertedType.equals("byte") && actualType.equals("Byte")) {
			return Type.BYTE_TYPE;
		}
		else if (convertedType.equals("short") && actualType.equals("Short")) {
			return Type.SHORT_TYPE;
		}
		else if (convertedType.equals("int") && actualType.equals("Integer")) {
			return Type.INT_TYPE;
		}
		else if (convertedType.equals("long") && actualType.equals("Long")) {
			return Type.LONG_TYPE;
		}
		else if (convertedType.equals("boolean") && actualType.equals("Boolean")) {
			return Type.BOOLEAN_TYPE;
		}
		else if (convertedType.equals("char") && actualType.equals("Character")	) {
			return Type.CHAR_TYPE;
		}
		else if (convertedType.equals("float") && actualType.equals("Float")) {
			return Type.FLOAT_TYPE;
		}
		else if (convertedType.equals("double") && actualType.equals("Double")) {
			return Type.DOUBLE_TYPE;
		}
		return null;
	}

	private Type checkForBoxing(String name, String desc, String methodOwner) {
		// for performance improvement
		if (!methodOwner.startsWith("java/lang/")
				|| !name.equals("valueOf")) {
			return null;
		}

		final String convertedType = methodOwner.substring(10);

		if (convertedType.equals("Byte") && desc.equals("(B)Ljava/lang/Byte;")) {
			return Type.BYTE_TYPE;
		}
		else if (convertedType.equals("Short") && desc.equals("(S)Ljava/lang/Short;")) {
			return Type.SHORT_TYPE;
		}
		else if (convertedType.equals("Integer") && desc.equals("(I)Ljava/lang/Integer;")) {
			return Type.INT_TYPE;
		}
		else if (convertedType.equals("Long") && desc.equals("(J)Ljava/lang/Long;")) {
			return Type.LONG_TYPE;
		}
		else if (convertedType.equals("Boolean") && desc.equals("(Z)Ljava/lang/Boolean;")) {
			return Type.BOOLEAN_TYPE;
		}
		else if (convertedType.equals("Character") && desc.equals("(C)Ljava/lang/Character;")) {
			return Type.CHAR_TYPE;
		}
		else if (convertedType.equals("Float") && desc.equals("(F)Ljava/lang/Float;")) {
			return Type.FLOAT_TYPE;
		}
		else if (convertedType.equals("Double") && desc.equals("(D)Ljava/lang/Double;")) {
			return Type.DOUBLE_TYPE;
		}
		return null;
	}

	// --------------------------------------------------------------------------------------------
	// Interpreter
	// --------------------------------------------------------------------------------------------

	// variable that maps the method's arguments to ASM values
	private int curArgIndex = -1;
	private List returnValues = new ArrayList();

	// see ModifiedASMFrame
	ModifiedASMFrame currentFrame;
	boolean rightMergePriority;

	@Override
	public BasicValue newValue(Type type) {
		TaggedValue tv;
		switch (analyzer.getState()) {
			// skip "return"
			case UdfAnalyzer.STATE_CAPTURE_RETURN:
				analyzer.setState(UdfAnalyzer.STATE_CAPTURE_THIS);
				curArgIndex++;
				return super.newValue(type);
			// tag "this"
			case UdfAnalyzer.STATE_CAPTURE_THIS:
				analyzer.setState(UdfAnalyzer.STATE_CAPTURE_INPUT1);
				tv = new TaggedValue(type, Tag.THIS);
				curArgIndex++;
				return tv;
			// tag input 1
			case UdfAnalyzer.STATE_CAPTURE_INPUT1:
				if (analyzer.isUdfBinary() || analyzer.isUdfReduceFunction()) {
					analyzer.setState(UdfAnalyzer.STATE_CAPTURE_INPUT2);
				} else if (analyzer.hasUdfCollector()) {
					analyzer.setState(UdfAnalyzer.STATE_CAPTURE_COLLECTOR);
				} else {
					analyzer.setState(UdfAnalyzer.STATE_END_OF_CAPTURING);
				}

				// input is iterable
				if (analyzer.hasUdfIterableInput()) {
					tv = new TaggedValue(type, Tag.INPUT_1_ITERABLE);
				}
				else {
					tv = analyzer.getInput1AsTaggedValue();
				}
				curArgIndex++;
				return tv;
			// tag input 2
			case UdfAnalyzer.STATE_CAPTURE_INPUT2:
				if (analyzer.hasUdfCollector()) {
					analyzer.setState(UdfAnalyzer.STATE_CAPTURE_COLLECTOR);
				} else {
					analyzer.setState(UdfAnalyzer.STATE_END_OF_CAPTURING);
				}

				// input is iterable
				if (analyzer.hasUdfIterableInput()) {
					tv = new TaggedValue(type, Tag.INPUT_2_ITERABLE);
				}
				// special case: reduce function
				else if (analyzer.isUdfReduceFunction()) {
					tv = analyzer.getInput1AsTaggedValue();
				}
				else {
					tv = analyzer.getInput2AsTaggedValue();
				}
				curArgIndex++;
				return tv;
			// tag collector
			case UdfAnalyzer.STATE_CAPTURE_COLLECTOR:
				analyzer.setState(UdfAnalyzer.STATE_END_OF_CAPTURING);
				tv = new TaggedValue(type, Tag.COLLECTOR);
				curArgIndex++;
				return tv;
			// if capturing has finished do not tag the arguments any more
			// but copy values where necessary (for nested methods)
			case UdfAnalyzer.STATE_END_OF_CAPTURING:
			default:
				// skip return type
				if (curArgIndex < 0) {
					curArgIndex++;
				}
				// return method's arguments
				else if (argumentValues != null && curArgIndex < argumentValues.size()) {
					return argumentValues.get(curArgIndex++);
				}
				return super.newValue(type);
		}
	}

	@Override
	public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
		switch (insn.getOpcode()) {
			case ACONST_NULL:
				return new TaggedValue(Type.getObjectType("null"), Tag.NULL);
			case NEW:
				analyzer.incrNewOperationCounters(topLevelMethod);
				// make new objects a tagged value to have possibility to tag an 
				// input container later
				return new TaggedValue(Type.getObjectType(((TypeInsnNode) insn).desc));
			// tag "int"-like constants
			case BIPUSH:
			case SIPUSH:
				final IntInsnNode intInsn = (IntInsnNode) insn;
				return new TaggedValue(intInsn.operand);
			case LDC:
				final Object cst = ((LdcInsnNode) insn).cst;
				if (cst instanceof Integer) {
					return new TaggedValue((Integer) cst);
				}
				return super.newOperation(insn);
			case ICONST_M1:
				return new TaggedValue(-1);
			case ICONST_0:
				return new TaggedValue(0);
			case ICONST_1:
				return new TaggedValue(1);
			case ICONST_2:
				return new TaggedValue(2);
			case ICONST_3:
				return new TaggedValue(3);
			case ICONST_4:
				return new TaggedValue(4);
			case ICONST_5:
				return new TaggedValue(5);
			default:
				return super.newOperation(insn);
		}
	}

	@Override
	public BasicValue copyOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException {
		switch (insn.getOpcode()) {
			case ILOAD:
				// int constants are only supported if they are used in the subsequent operation
				// otherwise we can not guarantee that it isn't modified elsewhere (e.g. do-while loop)
				if (isTagged(value) && tagged(value).isIntConstant()) {
					tagged(value).makeRegular();
				}
				return super.copyOperation(insn, value);
			// if input is stored "call by value", it will be copied
			case ISTORE:
			case LSTORE:
			case FSTORE:
			case DSTORE:
			case ASTORE:
			case DUP:
			case DUP_X1:
			case DUP_X2:
			case DUP2:
			case DUP2_X1:
			case DUP2_X2:
				if (isTagged(value) && tagged(value).isInput() && tagged(value).isCallByValue()) {
					return tagged(value).copy();
				}
			default:
				return super.copyOperation(insn, value);
		}
	}

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

	@Override
	public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2,
			BasicValue value3) throws AnalyzerException {
		// if array is an input, make it regular since the input is modified
		if (isTagged(value1) && tagged(value1).isInput()) {
			tagged(value1).makeRegular();
		}
		return super.ternaryOperation(insn, value1, value2, value3);
	}

	@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 values = (List) 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.parseInt(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 void returnOperation(AbstractInsnNode insn, BasicValue value,
			BasicValue expected) throws AnalyzerException {
		// return of a null value in the top level UDF method
		if (isTagged(value) && tagged(value).isNull() && topLevelMethod) {
			analyzer.handleNullReturn();
		}
		else if (hasImportantDependencies(value)){
			// add a copy and a reference
			// to capture the current state and future changes in alternative paths
			returnValues.add(tagged(value));
			returnValues.add(tagged(value).copy());
		}
		else {
			returnValues.add(null);
		}
	}

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy