package com.linkedin.parseq.lambda;
import java.util.function.Consumer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
class LambdaMethodVisitor extends MethodVisitor {
private SourcePointer _lambdaSourcePointer;
private Consumer<InferredOperation> _inferredOperationConsumer;
private Consumer<Integer> _lineNumberConsumer;
private boolean _visitedFirstInsn;
private boolean _containsSyntheticLambda;
private String _methodInsnOwner;
private String _methodInsnName;
private String _methodInsnDesc;
private int _methodInsnOpcode;
private ClassLoader _loader;
LambdaMethodVisitor(int api, MethodVisitor mv, SourcePointer lambdaSourcePointer,
Consumer<InferredOperation> inferredOperationConsumer,
Consumer<Integer> lineNumberConsumer,
ClassLoader loader) {
super(api, mv);
_lambdaSourcePointer = lambdaSourcePointer;
_inferredOperationConsumer = inferredOperationConsumer;
_lineNumberConsumer = lineNumberConsumer;
_visitedFirstInsn = false;
_containsSyntheticLambda = false;
_loader = loader;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (!_visitedFirstInsn) {
switch (opcode) {
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKESTATIC:
case Opcodes.INVOKEINTERFACE:
handleMethodInvoke(owner, name, desc, opcode);
_visitedFirstInsn = true;
break;
default:
//it should not come here as MethodVisitor API guarantees that it would either of the above 4 op codes.
//for details look at javadoc of MethodVisitor.visitMethodInsn
break;
}
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
public void visitEnd() {
if (_containsSyntheticLambda) {
String classToVisit = _methodInsnOwner.replace('/', '.');
SyntheticLambdaAnalyzer syntheticLambdaAnalyzer = new SyntheticLambdaAnalyzer(api, classToVisit, _methodInsnName);
ClassReader cr = getClassReader(classToVisit);
if (cr != null) {
cr.accept(syntheticLambdaAnalyzer, 0);
_inferredOperationConsumer.accept(new InferredOperation(syntheticLambdaAnalyzer.getInferredOperation()));
int inferredLineNumber = syntheticLambdaAnalyzer.getLineNumber();
if (inferredLineNumber != -1) {
_lineNumberConsumer.accept(inferredLineNumber);
}
}
} else {
//if it is static invocation, details about function could be found directly from the methodInsnName itself
if (_methodInsnOpcode == Opcodes.INVOKESTATIC) {
String functionName = Util.extractSimpleName(_methodInsnOwner, "/") + "::" + _methodInsnName;
_inferredOperationConsumer.accept(new InferredOperation(functionName));
} else {
String classToVisit = _lambdaSourcePointer._className.replace('/', '.');
FindMethodCallAnalyzer methodCallAnalyzer = new FindMethodCallAnalyzer(api, classToVisit, _lambdaSourcePointer, _methodInsnName);
ClassReader cr = getClassReader(classToVisit);
if (cr != null) {
cr.accept(methodCallAnalyzer, 0);
_inferredOperationConsumer.accept(new InferredOperation(methodCallAnalyzer.getInferredOperation()));
}
}
}
super.visitEnd();
}
private void handleMethodInvoke(String owner, String name, String desc, int opcode) {
_methodInsnName = name;
_methodInsnOwner = owner;
_methodInsnDesc = desc;
_methodInsnOpcode = opcode;
_containsSyntheticLambda = name.startsWith("lambda$");
}
private ClassReader getClassReader(String classToVisit) {
ClassReader cr = null;
try {
cr = new ClassReader(classToVisit);
} catch(Throwable e) {
try {
cr = new ClassReader(_loader.getResourceAsStream(classToVisit.replace(".", "/") + ".class"));
} catch (Throwable e1) {
System.out.println("Unable to read class: " + classToVisit);
}
}
return cr;
}
}