/* * 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.profiler.instrument.interceptor.InterceptorDefinition; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import java.util.List; /** * @author jaehong.kim */ public class ASMMethodNodeAdapter { private final String declaringClassInternalName; private final MethodNode methodNode; private final ASMMethodVariables methodVariables; public ASMMethodNodeAdapter(final String declaringClassInternalName, final MethodNode methodNode) { if (declaringClassInternalName == null || methodNode == null) { throw new IllegalArgumentException("declaring class internal name and method annotation must not be null. class=" + declaringClassInternalName + ", methodNode=" + methodNode); } if (methodNode.instructions == null || methodNode.desc == null) { throw new IllegalArgumentException("method annotation's instructions or desc must not be null. class=" + declaringClassInternalName + ", method=" + methodNode.name + methodNode.desc); } this.declaringClassInternalName = declaringClassInternalName; this.methodNode = methodNode; this.methodVariables = new ASMMethodVariables(declaringClassInternalName, methodNode); } public MethodNode getMethodNode() { return this.methodNode; } public String getDeclaringClassInternalName() { return this.declaringClassInternalName; } // find interceptor local variable. public boolean hasInterceptor() { return this.methodVariables.hasInterceptor(); } public String getName() { if (isConstructor()) { // simple class name. int index = this.declaringClassInternalName.lastIndexOf('/'); if (index < 0) { return this.declaringClassInternalName; } else { return this.declaringClassInternalName.substring(index + 1); } } return this.methodNode.name; } public void setName(final String name) { if (isConstructor()) { // skip. return; } this.methodNode.name = name; } public String[] getParameterTypes() { return this.methodVariables.getParameterTypes(); } public String[] getParameterNames() { return this.methodVariables.getParameterNames(); } public String getReturnType() { return this.methodVariables.getReturnType(); } public int getAccess() { return this.methodNode.access; } public void setAccess(final int access) { this.methodNode.access = access; } public boolean isConstructor() { return this.methodNode.name.equals("<init>"); } public String getDesc() { return this.methodNode.desc; } public int getLineNumber() { AbstractInsnNode node = this.methodNode.instructions.getFirst(); while (node != null) { if (node.getType() == AbstractInsnNode.LINE) { return ((LineNumberNode) node).line; } node = node.getNext(); } return 0; } public List<String> getExceptions() { return this.methodNode.exceptions; } public String getSignature() { return this.methodNode.signature; } public String getLongName() { return this.declaringClassInternalName + "/" + getName() + getDesc(); } public boolean isStatic() { return (this.methodNode.access & Opcodes.ACC_STATIC) != 0; } public boolean isAbstract() { return (this.methodNode.access & Opcodes.ACC_ABSTRACT) != 0; } public boolean isPrivate() { return (this.methodNode.access & Opcodes.ACC_PRIVATE) != 0; } public boolean isNative() { return (this.methodNode.access & Opcodes.ACC_NATIVE) != 0; } public boolean hasAnnotation(final Class<?> annotationClass) { if (annotationClass == null) { return false; } final String desc = Type.getDescriptor(annotationClass); return hasAnnotation(desc, this.methodNode.invisibleAnnotations) || hasAnnotation(desc, this.methodNode.visibleAnnotations); } private boolean hasAnnotation(final String annotationClassDesc, final List<AnnotationNode> annotationNodes) { if (annotationClassDesc == null || annotationNodes == null) { return false; } for (AnnotationNode annotation : annotationNodes) { if (annotation.desc != null && annotation.desc.equals(annotationClassDesc)) { return true; } } return false; } public void addDelegator(final String superClassInternalName) { if (superClassInternalName == null) { throw new IllegalArgumentException("super class internal name must not be null."); } final InsnList instructions = this.methodNode.instructions; if (isStatic()) { this.methodVariables.initLocalVariables(instructions); // load parameters this.methodVariables.loadArgs(instructions); // invoke static instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, superClassInternalName, this.methodNode.name, this.methodNode.desc, false)); } else { this.methodVariables.initLocalVariables(instructions); // load this this.methodVariables.loadVar(instructions, 0); // load parameters this.methodVariables.loadArgs(instructions); // invoke special instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, superClassInternalName, this.methodNode.name, this.methodNode.desc, false)); } // return this.methodVariables.returnValue(instructions); } public void rename(final String name) { if (name == null) { throw new IllegalArgumentException("method name must not be null."); } final ASMMethodInsnNodeRemapper remapper = new ASMMethodInsnNodeRemapper(); remapper.addFilter(this.declaringClassInternalName, this.methodNode.name, this.methodNode.desc); remapper.setName(name); // change recursive call. remapMethodInsnNode(remapper); // change name. this.methodNode.name = name; } public void remapMethodInsnNode(final ASMMethodInsnNodeRemapper remapper) { AbstractInsnNode insnNode = this.methodNode.instructions.getFirst(); while (insnNode != null) { if (insnNode instanceof MethodInsnNode) { final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; remapper.mapping(methodInsnNode); } insnNode = insnNode.getNext(); } } public void remapLocalVariables(final String name, final String desc) { if (methodNode.localVariables == null) { return; } final List<LocalVariableNode> localVariableNodes = methodNode.localVariables; for (LocalVariableNode node : localVariableNodes) { if (node.name.equals(name)) { node.desc = desc; } } } private void initInterceptorLocalVariables(final int interceptorId, final InterceptorDefinition interceptorDefinition, final int apiId) { final InsnList instructions = new InsnList(); if (this.methodVariables.initInterceptorLocalVariables(instructions, interceptorId, interceptorDefinition, apiId)) { // if first time. this.methodNode.instructions.insertBefore(this.methodVariables.getEnterInsnNode(), instructions); } } public void addBeforeInterceptor(final int interceptorId, final InterceptorDefinition interceptorDefinition, final int apiId) { initInterceptorLocalVariables(interceptorId, interceptorDefinition, apiId); final InsnList instructions = new InsnList(); this.methodVariables.loadInterceptorLocalVariables(instructions, interceptorDefinition, false); final String description = Type.getMethodDescriptor(interceptorDefinition.getBeforeMethod()); instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(interceptorDefinition.getInterceptorBaseClass()), "before", description, true)); this.methodNode.instructions.insertBefore(this.methodVariables.getEnterInsnNode(), instructions); } public void addAfterInterceptor(final int interceptorId, final InterceptorDefinition interceptorDefinition, final int apiId) { initInterceptorLocalVariables(interceptorId, interceptorDefinition, apiId); // add try catch block. final ASMTryCatch tryCatch = new ASMTryCatch(this.methodNode); this.methodNode.instructions.insertBefore(this.methodVariables.getEnterInsnNode(), tryCatch.getStartLabelNode()); this.methodNode.instructions.insert(this.methodVariables.getExitInsnNode(), tryCatch.getEndLabelNode()); // find return. AbstractInsnNode insnNode = this.methodNode.instructions.getFirst(); while (insnNode != null) { final int opcode = insnNode.getOpcode(); if (this.methodVariables.isReturnCode(opcode)) { final InsnList instructions = new InsnList(); this.methodVariables.storeResultVar(instructions, opcode); invokeAfterInterceptor(instructions, interceptorDefinition, false); this.methodNode.instructions.insertBefore(insnNode, instructions); } insnNode = insnNode.getNext(); } // try catch handler. InsnList instructions = new InsnList(); this.methodVariables.storeThrowableVar(instructions); invokeAfterInterceptor(instructions, interceptorDefinition, true); // throw exception. this.methodVariables.loadInterceptorThrowVar(instructions); this.methodNode.instructions.insert(tryCatch.getEndLabelNode(), instructions); tryCatch.sort(); } private void invokeAfterInterceptor(final InsnList instructions, final InterceptorDefinition interceptorDefinition, final boolean throwable) { this.methodVariables.loadInterceptorLocalVariables(instructions, interceptorDefinition, true); final String description = Type.getMethodDescriptor(interceptorDefinition.getAfterMethod()); instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(interceptorDefinition.getInterceptorBaseClass()), "after", description, true)); } }