package com.linkedin.parseq.lambda; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.StringJoiner; import org.objectweb.asm.ClassVisitor; 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.LabelNode; import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.LocalVariableNode; 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 function name and the line number of its invocation in source code, it analyzes class to infer information * such as number of parameters for function, field on which function is executed. */ class FindMethodCallAnalyzer extends ClassVisitor { private final String _classToAnalyze; private final String _methodToFind; private final int _lineNumberOfMethodCall; private final String _methodInsnName; private String _inferredOperation; FindMethodCallAnalyzer(int api, String classToAnalyze, SourcePointer sourcePointerOfMethodCall, String methodInsnName) { super(api); _classToAnalyze = classToAnalyze; _methodToFind = sourcePointerOfMethodCall._callingMethod; _lineNumberOfMethodCall = sourcePointerOfMethodCall._lineNumber; _methodInsnName = methodInsnName; } String getInferredOperation() { return _inferredOperation; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals(_methodToFind)) { return new FindMethodCallAnalyzer.FindMethodCallMethodVisitor(api, access, name, desc, signature, exceptions); } return super.visitMethod(access, name, desc, signature, exceptions); } private class FindMethodCallMethodVisitor extends MethodNode { FindMethodCallMethodVisitor(int api, int access, String name, String desc, String signature, String[] exceptions) { super(api, access, name, desc, signature, exceptions); } @Override public void visitEnd() { try { Analyzer analyzer = new Analyzer(new SourceInterpreter()); Frame[] frames = analyzer.analyze(_classToAnalyze, this); LabelNode label = findLineLabel(this.instructions, _lineNumberOfMethodCall); int index = findMethodCall(this.instructions, label); if (index != -1) { List<String> localVariables = new ArrayList<>(); String fieldDesc = ""; Frame f = frames[index]; boolean parsedThisOnce = false; for (int j = 0; j < f.getStackSize(); ++j) { SourceValue stack = (SourceValue) f.getStack(j); Object insn = stack.insns.iterator().next(); if (insn instanceof VarInsnNode) { VarInsnNode vinsn = (VarInsnNode) insn; if (vinsn.var < this.localVariables.size()) { String variable = ((LocalVariableNode) this.localVariables.get(vinsn.var)).name; //This part is tricky: discard the first this. if (variable.equals("this") && !parsedThisOnce) { parsedThisOnce = true; } else { localVariables.add(variable); } } } else if (insn instanceof FieldInsnNode) { FieldInsnNode fieldInstr = (FieldInsnNode) insn; fieldDesc = fieldInstr.name; } else if (insn instanceof TypeInsnNode) { fieldDesc = Util.getDescriptionForTypeInsnNode((TypeInsnNode) insn); } } _inferredOperation = getInferredOperation(localVariables, fieldDesc); } } catch (AnalyzerException e) { System.out.println("Unable to analyze class, could not infer operation"); } } private LabelNode findLineLabel(InsnList insns, int line) { for (Iterator it = insns.iterator(); it.hasNext(); ) { Object n = it.next(); if (n instanceof LineNumberNode && ((LineNumberNode) n).line == line) { return ((LineNumberNode) n).start; } } return null; } private int findMethodCall(InsnList insns, LabelNode label) { boolean foundLabel = false; for (int i = 0; i < insns.size(); i++) { AbstractInsnNode n = insns.get(i); if (!foundLabel && n == label) { foundLabel = true; } else if (foundLabel && n.getOpcode() == Opcodes.INVOKEDYNAMIC) { return i; } } return -1; } //Keeping the code commented if we were to improve this functionality in future private String getInferredOperation(List<String> localVariables, String fieldDesc) { // String localVarsDesc = getDescriptionForLocalVars(localVariables); StringBuilder sb = new StringBuilder(); // if (!fieldDesc.isEmpty()) { // sb.append(fieldDesc).append("::"); // } else if (!localVarsDesc.isEmpty()) { // sb.append(localVarsDesc).append("::"); // } else if (!methodDesc.isEmpty()) { // sb.append(methodDesc).append("::"); // } sb.append("::" + _methodInsnName); return sb.toString(); } } private static String getDescriptionForLocalVars(List<String> variables) { if (variables == null || variables.size() == 0) { return ""; } if (variables.size() == 1) { return variables.get(0); } StringJoiner sj = new StringJoiner(",", "(", ")"); variables.forEach(sj::add); return sj.toString(); } }