/* * Copyright 2016 NAVER Corp. * * Licensed 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 com.navercorp.pinpoint.profiler.instrument; import com.navercorp.pinpoint.bootstrap.interceptor.Interceptor; import com.navercorp.pinpoint.bootstrap.interceptor.registry.InterceptorRegistry; import com.navercorp.pinpoint.profiler.instrument.interceptor.InterceptorDefinition; import com.navercorp.pinpoint.profiler.instrument.interceptor.InterceptorType; import com.navercorp.pinpoint.profiler.util.JavaAssistUtils; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.Method; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.IntInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.LocalVariableNode; 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 java.util.Collections; import java.util.Comparator; import java.util.List; /** * @author jaehong.kim */ public class ASMMethodVariables { private static final Type BYTE_TYPE = Type.getObjectType("java/lang/Byte"); private static final Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean"); private static final Type SHORT_TYPE = Type.getObjectType("java/lang/Short"); private static final Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character"); private static final Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer"); private static final Type FLOAT_TYPE = Type.getObjectType("java/lang/Float"); private static final Type LONG_TYPE = Type.getObjectType("java/lang/Long"); private static final Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double"); private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); private final LabelNode interceptorVariableStartLabelNode = new LabelNode(); private final LabelNode interceptorVariableEndLabelNode = new LabelNode(); private final String declaringClassInternalName; private final MethodNode methodNode; private final Type[] argumentTypes; private final Type returnType; private boolean initializedInterceptorLocalVariables = false; private AbstractInsnNode enterInsnNode; private AbstractInsnNode exitInsnNode; private int nextLocals; private int interceptorVarIndex; private int arg0VarIndex; private int arg1VarIndex; private int arg2VarIndex; private int arg3VarIndex; private int arg4VarIndex; private int argsVarIndex; private int classNameVarIndex; private int methodNameVarIndex; private int parameterDescriptionVarIndex; private int apiIdVarIndex; private int resultVarIndex; private int throwableVarIndex; public ASMMethodVariables(final String declaringClassInternalName, final MethodNode methodNode) { this.declaringClassInternalName = declaringClassInternalName; this.methodNode = methodNode; this.nextLocals = methodNode.maxLocals; this.argumentTypes = Type.getArgumentTypes(methodNode.desc); this.returnType = Type.getReturnType(methodNode.desc); } public AbstractInsnNode getEnterInsnNode() { return enterInsnNode; } public AbstractInsnNode getExitInsnNode() { return exitInsnNode; } public String[] getParameterTypes() { final String[] parameterTypes = new String[this.argumentTypes.length]; for (int i = 0; i < this.argumentTypes.length; i++) { parameterTypes[i] = this.argumentTypes[i].getClassName(); } return parameterTypes; } public String[] getParameterNames() { if (this.argumentTypes.length == 0) { return new String[0]; } final List<LocalVariableNode> localVariableNodes = this.methodNode.localVariables; int localVariableStartIndex = 1; if (isStatic()) { // static method is none this. localVariableStartIndex = 0; } if (localVariableNodes == null || localVariableNodes.size() <= localVariableStartIndex || (this.argumentTypes.length + localVariableStartIndex) > localVariableNodes.size()) { // make simple argument names. final String[] names = new String[this.argumentTypes.length]; for (int i = 0; i < this.argumentTypes.length; i++) { final String className = this.argumentTypes[i].getClassName(); if (className != null) { final int findIndex = className.lastIndexOf('.'); if (findIndex == -1) { names[i] = className; } else { names[i] = className.substring(findIndex + 1); } } else { names[i] = this.argumentTypes[i].getDescriptor(); } } return names; } // sort by index. Collections.sort(localVariableNodes, new Comparator<LocalVariableNode>() { @Override public int compare(LocalVariableNode o1, LocalVariableNode o2) { return o1.index - o2.index; } }); String[] names = new String[this.argumentTypes.length]; for (int i = 0; i < this.argumentTypes.length; i++) { final String name = localVariableNodes.get(localVariableStartIndex++).name; if (name != null) { names[i] = name; } else { names[i] = ""; } } return names; } public String getReturnType() { return this.returnType.getClassName(); } public boolean hasInterceptor() { final List<LocalVariableNode> localVariableNodes = this.methodNode.localVariables; if (localVariableNodes == null) { return false; } for (LocalVariableNode node : localVariableNodes) { if (node.name.equals("_$PINPOINT$_interceptor")) { return true; } } return false; } // new method only. public void initLocalVariables(final InsnList instructions) { // find enter & exit instruction. final LabelNode variableStartLabelNode = new LabelNode(); final LabelNode variableEndLabelNode = new LabelNode(); if(instructions.getFirst() != null) { instructions.insertBefore(instructions.getFirst(), variableStartLabelNode); } else { instructions.insert(variableStartLabelNode); } instructions.insert(instructions.getLast(), variableEndLabelNode); if (!isStatic()) { addLocalVariable("this", Type.getObjectType(this.declaringClassInternalName).getDescriptor(), variableStartLabelNode, variableEndLabelNode); } for (Type type : this.argumentTypes) { addLocalVariable(JavaAssistUtils.javaClassNameToVariableName(type.getClassName()), type.getDescriptor(), variableStartLabelNode, variableEndLabelNode); } } public boolean initInterceptorLocalVariables(final InsnList instructions, final int interceptorId, final InterceptorDefinition interceptorDefinition, final int apiId) { if (this.initializedInterceptorLocalVariables) { return false; } this.initializedInterceptorLocalVariables = true; // find enter & exit instruction. if (isConstructor()) { this.enterInsnNode = findInitConstructorInstruction(); } else { this.enterInsnNode = methodNode.instructions.getFirst(); } if (this.enterInsnNode == null) { throw new IllegalStateException("not found enter code. " + declaringClassInternalName + "/" + methodNode.name + methodNode.desc); } this.exitInsnNode = methodNode.instructions.getLast(); // setup interceptor variables start/end label. this.methodNode.instructions.insertBefore(this.enterInsnNode, this.interceptorVariableStartLabelNode); this.methodNode.instructions.insert(this.exitInsnNode, this.interceptorVariableEndLabelNode); // initialize interceptor variable. initInterceptorVar(instructions, interceptorId); // initialize argument variable. final InterceptorType interceptorType = interceptorDefinition.getInterceptorType(); if (interceptorType == InterceptorType.ARRAY_ARGS) { // Object target, Object[] args initArgsVar(instructions); } else if (interceptorType == InterceptorType.STATIC) { // Object target, String declaringClassInternalName, String methodName, String parameterDescription, Object[] args initClassNameVar(instructions); initMethodNameVar(instructions); initParameterDescriptionVar(instructions); initArgsVar(instructions); } else if (interceptorType == InterceptorType.API_ID_AWARE) { // Object target, int apiId, Object[] args initApiIdVar(apiId, instructions); initArgsVar(instructions); } else if (interceptorType == InterceptorType.BASIC) { int interceptorMethodParameterCount = getInterceptorParameterCount(interceptorDefinition); final int methodParameterCount = this.argumentTypes.length; final int argumentCount = Math.min(methodParameterCount, interceptorMethodParameterCount); for (int i = 1; i <= argumentCount; i++) { // Object target, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4 if (i == 1) { initArg0Var(instructions); } else if (i == 2) { initArg1Var(instructions); } else if (i == 3) { initArg2Var(instructions); } else if (i == 4) { initArg3Var(instructions); } else if (i == 5) { initArg4Var(instructions); } } } return true; } AbstractInsnNode findInitConstructorInstruction() { int nested = 0; for (AbstractInsnNode insnNode = this.methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode.getNext()) { if (insnNode instanceof TypeInsnNode) { if (insnNode.getOpcode() == Opcodes.NEW) { // new object(). nested++; } } else if (insnNode instanceof MethodInsnNode) { final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; if (methodInsnNode.getOpcode() == Opcodes.INVOKESPECIAL && methodInsnNode.name.equals("<init>")) { if (--nested < 0) { // find this() or super(). return insnNode.getNext(); } } } } return null; } private void initInterceptorVar(final InsnList instructions, final int interceptorId) { assertInitializedInterceptorLocalVariables(); this.interceptorVarIndex = addInterceptorLocalVariable("_$PINPOINT$_interceptor", "Lcom/navercorp/pinpoint/bootstrap/interceptor/Interceptor;"); push(instructions, interceptorId); instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(InterceptorRegistry.class), "getInterceptor", "(I)" + Type.getDescriptor(Interceptor.class), false)); storeVar(instructions, this.interceptorVarIndex); this.resultVarIndex = addInterceptorLocalVariable("_$PINPOINT$_result", "Ljava/lang/Object;"); loadNull(instructions); storeVar(instructions, this.resultVarIndex); this.throwableVarIndex = addInterceptorLocalVariable("_$PINPOINT$_throwable", "Ljava/lang/Throwable;"); loadNull(instructions); storeVar(instructions, this.throwableVarIndex); } private void initArgsVar(final InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.argsVarIndex = addInterceptorLocalVariable("_$PINPOINT$_args", "[Ljava/lang/Object;"); loadArgsVar(instructions); storeVar(instructions, this.argsVarIndex); } private void initClassNameVar(InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.classNameVarIndex = addInterceptorLocalVariable("_$PINPOINT$_className", "Ljava/lang/String;"); push(instructions, JavaAssistUtils.jvmNameToJavaName(this.declaringClassInternalName)); storeVar(instructions, this.classNameVarIndex); } private void initMethodNameVar(InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.methodNameVarIndex = addInterceptorLocalVariable("_$PINPOINT$_methodName", "Ljava/lang/String;"); push(instructions, this.methodNode.name); storeVar(instructions, this.methodNameVarIndex); } private void initParameterDescriptionVar(InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.parameterDescriptionVarIndex = addInterceptorLocalVariable("_$PINPOINT$_parameterDescription", "Ljava/lang/String;"); push(instructions, this.methodNode.desc); storeVar(instructions, this.parameterDescriptionVarIndex); } private void initApiIdVar(int apiId, InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.apiIdVarIndex = addInterceptorLocalVariable("_$PINPOINT$_apiId", "I"); push(instructions, apiId); storeInt(instructions, this.apiIdVarIndex); } private void initArg0Var(InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.arg0VarIndex = addInterceptorLocalVariable("_$PINPOINT$_arg0", "Ljava/lang/String;"); loadArg(instructions, this.argumentTypes, 0); box(instructions, this.argumentTypes[0]); storeVar(instructions, this.arg0VarIndex); } private void initArg1Var(InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.arg1VarIndex = addInterceptorLocalVariable("_$PINPOINT$_arg1", "Ljava/lang/String;"); loadArg(instructions, this.argumentTypes, 1); box(instructions, this.argumentTypes[1]); storeVar(instructions, this.arg1VarIndex); } private void initArg2Var(InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.arg2VarIndex = addInterceptorLocalVariable("_$PINPOINT$_arg2", "Ljava/lang/String;"); loadArg(instructions, this.argumentTypes, 2); box(instructions, this.argumentTypes[2]); storeVar(instructions, this.arg2VarIndex); } private void initArg3Var(InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.arg3VarIndex = addInterceptorLocalVariable("_$PINPOINT$_arg3", "Ljava/lang/String;"); loadArg(instructions, this.argumentTypes, 3); box(instructions, this.argumentTypes[3]); storeVar(instructions, this.arg3VarIndex); } private void initArg4Var(InsnList instructions) { assertInitializedInterceptorLocalVariables(); this.arg4VarIndex = addInterceptorLocalVariable("_$PINPOINT$_arg4", "Ljava/lang/String;"); loadArg(instructions, this.argumentTypes, 4); box(instructions, this.argumentTypes[4]); storeVar(instructions, this.arg4VarIndex); } int getInterceptorParameterCount(final InterceptorDefinition interceptorDefinition) { if (interceptorDefinition.getBeforeMethod() != null) { // skip this. return interceptorDefinition.getBeforeMethod().getParameterTypes().length - 1; } else if (interceptorDefinition.getAfterMethod() != null) { // skip this, result, throwable. return interceptorDefinition.getAfterMethod().getParameterTypes().length - 3; } return 0; } public void storeThrowableVar(final InsnList instructions) { assertInitializedInterceptorLocalVariables(); storeVar(instructions, this.throwableVarIndex); loadNull(instructions); storeVar(instructions, this.resultVarIndex); } public void storeResultVar(final InsnList instructions, final int opcode) { assertInitializedInterceptorLocalVariables(); if (opcode == Opcodes.RETURN) { // void. loadNull(instructions); } else if (opcode == Opcodes.ARETURN) { // object. dup(instructions); } else { if (opcode == Opcodes.LRETURN || opcode == Opcodes.DRETURN) { // long or double. dup2(instructions); } else { dup(instructions); } final Type type = Type.getReturnType(this.methodNode.desc); box(instructions, type); } storeVar(instructions, this.resultVarIndex); loadNull(instructions); storeVar(instructions, this.throwableVarIndex); } public void loadInterceptorLocalVariables(final InsnList instructions, final InterceptorDefinition interceptorDefinition, final boolean after) { assertInitializedInterceptorLocalVariables(); loadVar(instructions, this.interceptorVarIndex); instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(interceptorDefinition.getInterceptorBaseClass()))); // target(this) object. loadThis(instructions); final InterceptorType interceptorType = interceptorDefinition.getInterceptorType(); if (interceptorType == InterceptorType.ARRAY_ARGS) { // Object target, Object[] args loadVar(instructions, this.argsVarIndex); } else if (interceptorType == InterceptorType.STATIC) { // Object target, String declaringClassInternalName, String methodName, String parameterDescription, Object[] args loadVar(instructions, this.classNameVarIndex); loadVar(instructions, this.methodNameVarIndex); loadVar(instructions, this.parameterDescriptionVarIndex); loadVar(instructions, this.argsVarIndex); } else if (interceptorType == InterceptorType.API_ID_AWARE) { // Object target, int apiId, Object[] args loadInt(instructions, this.apiIdVarIndex); loadVar(instructions, this.argsVarIndex); } else if (interceptorType == InterceptorType.BASIC) { int interceptorMethodParameterCount = getInterceptorParameterCount(interceptorDefinition); int argumentCount = Math.min(this.argumentTypes.length, interceptorMethodParameterCount); int i = 1; for (; i <= argumentCount; i++) { // Object target, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4 if (i == 1) loadVar(instructions, this.arg0VarIndex); else if (i == 2) loadVar(instructions, this.arg1VarIndex); else if (i == 3) loadVar(instructions, this.arg2VarIndex); else if (i == 4) loadVar(instructions, this.arg3VarIndex); else if (i == 5) loadVar(instructions, this.arg4VarIndex); else loadNull(instructions); } for (; i <= interceptorMethodParameterCount; i++) { loadNull(instructions); } } if (after) { loadVar(instructions, this.resultVarIndex); loadVar(instructions, this.throwableVarIndex); } } public void loadInterceptorThrowVar(final InsnList instructions) { assertInitializedInterceptorLocalVariables(); loadVar(instructions, this.throwableVarIndex); instructions.add(new InsnNode(Opcodes.ATHROW)); } void loadThis(final InsnList instructions) { if (isConstructor()) { // load this. loadVar(instructions, 0); } else { if (isStatic()) { // load null. loadNull(instructions); } else { // load this. loadVar(instructions, 0); } } } void storeVar(final InsnList instructions, final int index) { instructions.add(new VarInsnNode(Opcodes.ASTORE, index)); } void storeInt(final InsnList instructions, final int index) { instructions.add(new VarInsnNode(Opcodes.ISTORE, index)); } void loadNull(final InsnList instructions) { instructions.add(new InsnNode(Opcodes.ACONST_NULL)); } void loadVar(final InsnList instructions, final int index) { instructions.add(new VarInsnNode(Opcodes.ALOAD, index)); } void loadInt(final InsnList instructions, final int index) { instructions.add(new VarInsnNode(Opcodes.ILOAD, index)); } boolean isReturnCode(final int opcode) { return opcode == Opcodes.IRETURN || opcode == Opcodes.LRETURN || opcode == Opcodes.FRETURN || opcode == Opcodes.DRETURN || opcode == Opcodes.ARETURN || opcode == Opcodes.RETURN; } Type getBoxedType(final Type type) { switch (type.getSort()) { case Type.BYTE: return BYTE_TYPE; case Type.BOOLEAN: return BOOLEAN_TYPE; case Type.SHORT: return SHORT_TYPE; case Type.CHAR: return CHARACTER_TYPE; case Type.INT: return INTEGER_TYPE; case Type.FLOAT: return FLOAT_TYPE; case Type.LONG: return LONG_TYPE; case Type.DOUBLE: return DOUBLE_TYPE; } return type; } void push(InsnList insnList, final int value) { if (value >= -1 && value <= 5) { insnList.add(new InsnNode(Opcodes.ICONST_0 + value)); } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { insnList.add(new IntInsnNode(Opcodes.BIPUSH, value)); } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { insnList.add(new IntInsnNode(Opcodes.SIPUSH, value)); } else { insnList.add(new LdcInsnNode(value)); } } void push(InsnList insnList, final String value) { if (value == null) { insnList.add(new InsnNode(Opcodes.ACONST_NULL)); } else { insnList.add(new LdcInsnNode(value)); } } void newArray(final InsnList insnList, final Type type) { insnList.add(new TypeInsnNode(Opcodes.ANEWARRAY, type.getInternalName())); } void dup(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.DUP)); } void dup2(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.DUP2)); } void dupX1(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.DUP_X1)); } void dupX2(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.DUP_X2)); } void pop(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.POP)); } void swap(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.SWAP)); } void loadArgsVar(final InsnList instructions) { if (this.argumentTypes.length == 0) { // null. loadNull(instructions); return; } push(instructions, this.argumentTypes.length); // new array newArray(instructions, OBJECT_TYPE); for (int i = 0; i < this.argumentTypes.length; i++) { Type type = this.argumentTypes[i]; dup(instructions); push(instructions, i); // loadArg loadArg(instructions, this.argumentTypes, i); // box box(instructions, type); // arrayStore arrayStore(instructions, OBJECT_TYPE); } } void loadArgs(final InsnList instructions) { for (int i = 0; i < this.argumentTypes.length; i++) { loadArg(instructions, this.argumentTypes, i); } } void loadArg(final InsnList instructions, Type[] argumentTypes, int i) { final int index = getArgIndex(argumentTypes, i); final Type type = argumentTypes[i]; instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), index)); } int getArgIndex(final Type[] argumentTypes, final int arg) { int index = isStatic() ? 0 : 1; for (int i = 0; i < arg; i++) { index += argumentTypes[i].getSize(); } return index; } void box(final InsnList instructions, Type type) { if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { return; } if (type == Type.VOID_TYPE) { // push null instructions.add(new InsnNode(Opcodes.ACONST_NULL)); } else { Type boxed = getBoxedType(type); // new instance. newInstance(instructions, boxed); if (type.getSize() == 2) { // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o // dupX2 dupX2(instructions); // dupX2 dupX2(instructions); // pop pop(instructions); } else { // p -> po -> opo -> oop -> o // dupX1 dupX1(instructions); // swap swap(instructions); } invokeConstructor(instructions, boxed, new Method("<init>", Type.VOID_TYPE, new Type[]{type})); } } void arrayStore(final InsnList instructions, final Type type) { instructions.add(new InsnNode(type.getOpcode(Opcodes.IASTORE))); } void newInstance(final InsnList instructions, final Type type) { instructions.add(new TypeInsnNode(Opcodes.NEW, type.getInternalName())); } void invokeConstructor(final InsnList instructions, final Type type, final Method method) { String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName(); instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, owner, method.getName(), method.getDescriptor(), false)); } int addInterceptorLocalVariable(final String name, final String desc) { return addLocalVariable(name, desc, this.interceptorVariableStartLabelNode, this.interceptorVariableEndLabelNode); } int addLocalVariable(final String name, final String desc, final LabelNode start, final LabelNode end) { int index = this.nextLocals; this.nextLocals += 1; final LocalVariableNode node = new LocalVariableNode(name, desc, null, start, end, index); this.methodNode.localVariables.add(node); return index; } public void returnValue(final InsnList instructions) { instructions.add(new InsnNode(this.returnType.getOpcode(Opcodes.IRETURN))); } private boolean isStatic() { return (this.methodNode.access & Opcodes.ACC_STATIC) != 0; } private boolean isConstructor() { return this.methodNode.name != null && this.methodNode.name.equals("<init>"); } private void assertInitializedInterceptorLocalVariables() { if (!this.initializedInterceptorLocalVariables) { throw new IllegalStateException("The interceptor local variables must be initialized."); } } // test only. List<LocalVariableNode> getLocalVariables() { return this.methodNode.localVariables; } }