package com.linkedin.parseq.lambda;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SourceInterpreter;
import org.objectweb.asm.tree.analysis.SourceValue;
/**
* Given the uniquely generated synthetic lambda function, it analyzes class to infer information such as number of
* parameters for the lambda function
*/
class SyntheticLambdaAnalyzer extends ClassVisitor {
private final String _classToAnalyze;
private final String _methodToFind;
private String _inferredOperation;
private int _lineNumber = -1;
SyntheticLambdaAnalyzer(int api, String classToAnalyze, String methodToFind) {
super(api);
_classToAnalyze = classToAnalyze;
_methodToFind = methodToFind;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals(_methodToFind)) {
return new SyntheticLambdaMethodVisitor(api, access, name, desc, signature, exceptions);
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
String getInferredOperation() {
return _inferredOperation;
}
int getLineNumber() {
return _lineNumber;
}
class SyntheticLambdaMethodVisitor extends MethodNode {
private String _methodInsnName;
private String _methodInsnOwner;
private String _methodInsnDesc;
private int _methodInsnOpcode;
SyntheticLambdaMethodVisitor(int api, int access, String name, String desc, String signature, String[] exceptions) {
super(api, access, name, desc, signature, exceptions);
}
@Override
public void visitLineNumber(int line, Label start) {
if (_lineNumber == -1) {
_lineNumber = line;
}
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
switch (opcode) {
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKESTATIC:
case Opcodes.INVOKEINTERFACE:
_methodInsnName = name;
_methodInsnOwner = owner;
_methodInsnDesc = desc;
_methodInsnOpcode = opcode;
break;
default:
System.out.println("Unexpected opcode, falling back");
break;
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
public void visitEnd() {
try {
Analyzer analyzer = new Analyzer(new SourceInterpreter());
Frame[] frames = analyzer.analyze(_classToAnalyze, this);
int index = findMethodCall(this.instructions);
if (index == -1) {
return;
}
Frame f = frames[index];
String fieldDesc = "";
String methodDesc = "";
for (int j = 0; j < f.getStackSize(); ++j) {
SourceValue stack = (SourceValue) f.getStack(j);
Object insn = stack.insns.iterator().next();
if (insn instanceof FieldInsnNode) {
FieldInsnNode fieldInstr = (FieldInsnNode) insn;
fieldDesc = fieldInstr.name;
} else if (insn instanceof TypeInsnNode) {
fieldDesc = Util.getDescriptionForTypeInsnNode((TypeInsnNode) insn);
} else if (insn instanceof MethodInsnNode) {
methodDesc = Util.getDescriptionForMethodInsnNode((MethodInsnNode) insn);
}
}
_inferredOperation = getInferredOperation(fieldDesc, methodDesc);
} catch (AnalyzerException e) {
System.out.println("Unable to analyze class, could not infer operation");
}
}
//find the last operation
private int findMethodCall(InsnList insns) {
int ret = -1;
boolean encounteredLineNode = false;
int count = 0;
for (int i = 0; i < insns.size(); i++) {
AbstractInsnNode n = insns.get(i);
if (!(n instanceof LabelNode
|| n instanceof LineNumberNode
|| n instanceof VarInsnNode
|| n instanceof InvokeDynamicInsnNode
|| n instanceof FieldInsnNode
|| n instanceof InsnNode
|| n instanceof IntInsnNode
|| n instanceof LdcInsnNode
|| n instanceof MethodInsnNode
|| n instanceof TypeInsnNode)) {
return -1;
}
if (n instanceof LineNumberNode) {
if (encounteredLineNode) {
//if code is split across multiple lines, lets fail
return -1;
}
encounteredLineNode = true;
}
if (n.getOpcode() == Opcodes.INVOKEVIRTUAL
|| n.getOpcode() == Opcodes.INVOKESTATIC
|| n.getOpcode() == Opcodes.INVOKEINTERFACE
|| n.getOpcode() == Opcodes.INVOKESPECIAL) {
ret = i;
count++;
}
}
if (count > 2) {
//lets fail when we see more than 2 invocations of any type
return -1;
}
return ret;
}
private String getInferredOperation(String fieldDesc, String methodDesc) {
String functionName;
if (_methodInsnOpcode == Opcodes.INVOKESTATIC) {
//if the last instruction is autoboxing and instruction before that is identifiable then return that previous
//method description
if (_methodInsnName.equals("valueOf")
&& _methodInsnOwner.startsWith("java/lang")) {
if (!methodDesc.isEmpty()) {
return methodDesc;
} else {
return "";
}
} else {
functionName = Util.extractSimpleName(_methodInsnOwner, "/") + "." + _methodInsnName;
}
} else if (_methodInsnOpcode == Opcodes.INVOKESPECIAL && _methodInsnName.equals("<init>")) {
functionName = "new " + Util.extractSimpleName(_methodInsnOwner, "/");
} else {
functionName = _methodInsnName;
}
return functionName + Util.getArgumentsInformation(_methodInsnDesc);
}
}
}