/* * 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.instrument.InstrumentContext; import com.navercorp.pinpoint.profiler.util.JavaAssistUtils; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.InnerClassNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * @author jaehong.kim */ public class ASMClassNodeAdapter { public static ASMClassNodeAdapter get(final InstrumentContext pluginContext, final ClassLoader classLoader, final String classInternalName) { return get(pluginContext, classLoader, classInternalName, false); } public static ASMClassNodeAdapter get(final InstrumentContext pluginContext, final ClassLoader classLoader, final String classInternalName, final boolean skipCode) { if (pluginContext == null || classInternalName == null) { throw new IllegalArgumentException("plugin context or class name must not be null."); } InputStream in = null; try { in = pluginContext.getResourceAsStream(classLoader, classInternalName + ".class"); if (in != null) { final ClassReader classReader = new ClassReader(in); final ClassNode classNode = new ClassNode(); if (skipCode) { classReader.accept(classNode, ClassReader.SKIP_CODE); } else { classReader.accept(classNode, 0); } return new ASMClassNodeAdapter(pluginContext, classLoader, classNode, skipCode); } } catch (IOException ignored) { // not found class. } finally { if (in != null) { try { in.close(); } catch (IOException ignored) { } } } return null; } private final InstrumentContext pluginContext; private final ClassLoader classLoader; private final ClassNode classNode; private final boolean skipCode; public ASMClassNodeAdapter(final InstrumentContext pluginContext, final ClassLoader classLoader, final ClassNode classNode) { this(pluginContext, classLoader, classNode, false); } public ASMClassNodeAdapter(final InstrumentContext pluginContext, final ClassLoader classLoader, final ClassNode classNode, final boolean skipCode) { this.pluginContext = pluginContext; this.classLoader = classLoader; this.classNode = classNode; this.skipCode = skipCode; } public String getInternalName() { return this.classNode.name; } public String getName() { return this.classNode.name == null ? null : JavaAssistUtils.jvmNameToJavaName(this.classNode.name); } public String getSuperClassInternalName() { return this.classNode.superName; } public String getSuperClassName() { return this.classNode.superName == null ? null : JavaAssistUtils.jvmNameToJavaName(this.classNode.superName); } public boolean isInterface() { return (classNode.access & Opcodes.ACC_INTERFACE) != 0; } public boolean isAnnotation() { return (classNode.access & Opcodes.ACC_ANNOTATION) != 0; } public String[] getInterfaceNames() { final List<String> interfaces = this.classNode.interfaces; if (interfaces == null || interfaces.size() == 0) { return new String[0]; } final List<String> list = new ArrayList<String>(); for (String name : interfaces) { if (name != null) { list.add(JavaAssistUtils.jvmNameToJavaName(name)); } } return list.toArray(new String[list.size()]); } public ASMMethodNodeAdapter getDeclaredMethod(final String methodName, final String desc) { if (this.skipCode) { throw new IllegalStateException("not supported operation, skipCode option is true."); } return findDeclaredMethod(methodName, desc); } public boolean hasDeclaredMethod(final String methodName, final String desc) { return findDeclaredMethod(methodName, desc) != null; } private ASMMethodNodeAdapter findDeclaredMethod(final String methodName, final String desc) { if (methodName == null) { return null; } final List<MethodNode> declaredMethods = classNode.methods; if (declaredMethods == null) { return null; } for (MethodNode methodNode : declaredMethods) { if (methodNode.name == null || !methodNode.name.equals(methodName)) { continue; } if (desc == null || (methodNode.desc != null && methodNode.desc.startsWith(desc))) { return new ASMMethodNodeAdapter(getInternalName(), methodNode); } } return null; } public List<ASMMethodNodeAdapter> getDeclaredMethods() { if (this.skipCode) { throw new IllegalStateException("not supported operation, skipCode option is true."); } final List<ASMMethodNodeAdapter> methodNodes = new ArrayList<ASMMethodNodeAdapter>(); if (this.classNode.methods == null) { return methodNodes; } for (MethodNode methodNode : this.classNode.methods) { if (methodNode.name == null || methodNode.name.equals("<init>") || methodNode.name.equals("<clinit>")) { // skip constructor(<init>) and static initializer block(<clinit>) continue; } methodNodes.add(new ASMMethodNodeAdapter(getInternalName(), methodNode)); } return methodNodes; } public boolean hasOutClass(final String methodName, final String desc) { if (methodName == null || this.classNode.outerClass == null || this.classNode.outerMethod == null || !this.classNode.outerMethod.equals(methodName)) { return false; } if (desc == null || (this.classNode.outerMethodDesc != null && this.classNode.outerMethodDesc.startsWith(desc))) { return true; } return false; } public boolean hasMethod(final String methodName, final String desc) { if (hasDeclaredMethod(methodName, desc)) { return true; } if (this.classNode.superName != null) { // skip code. final ASMClassNodeAdapter classNode = ASMClassNodeAdapter.get(this.pluginContext, this.classLoader, this.classNode.superName, true); if (classNode != null) { return classNode.hasMethod(methodName, desc); } } return false; } public ASMFieldNodeAdapter getField(final String fieldName, final String fieldDesc) { if (fieldName == null || this.classNode.fields == null) { return null; } final List<FieldNode> fields = this.classNode.fields; for (FieldNode fieldNode : fields) { if ((fieldNode.name != null && fieldNode.name.equals(fieldName)) && (fieldDesc == null || (fieldNode.desc != null && fieldNode.desc.equals(fieldDesc)))) { return new ASMFieldNodeAdapter(fieldNode); } } // find interface. final List<String> interfaces = this.classNode.interfaces; if (interfaces != null && interfaces.size() > 0) { for (String interfaceClassName : interfaces) { if (interfaceClassName == null) { continue; } final ASMClassNodeAdapter classNodeAdapter = ASMClassNodeAdapter.get(this.pluginContext, this.classLoader, interfaceClassName, true); if (classNodeAdapter != null) { final ASMFieldNodeAdapter fieldNode = classNodeAdapter.getField(fieldName, fieldDesc); if (fieldNode != null) { return fieldNode; } } } } // find super class. if (this.classNode.superName != null) { final ASMClassNodeAdapter classNodeAdapter = ASMClassNodeAdapter.get(this.pluginContext, this.classLoader, this.classNode.superName, true); if (classNodeAdapter != null) { final ASMFieldNodeAdapter fieldNode = classNodeAdapter.getField(fieldName, fieldDesc); if (fieldNode != null) { return fieldNode; } } } return null; } public ASMFieldNodeAdapter addField(final String fieldName, final Class<?> fieldClass) { if (fieldName == null || fieldClass == null) { throw new IllegalArgumentException("fieldNode name or fieldNode class must not be null."); } final Type type = Type.getType(fieldClass); final FieldNode fieldNode = new FieldNode(Opcodes.ACC_PRIVATE, fieldName, type.getDescriptor(), null, null); if (this.classNode.fields == null) { this.classNode.fields = new ArrayList<FieldNode>(); } this.classNode.fields.add(fieldNode); return new ASMFieldNodeAdapter(fieldNode); } public ASMMethodNodeAdapter addDelegatorMethod(final ASMMethodNodeAdapter superMethodNode) { if (superMethodNode == null) { throw new IllegalArgumentException("super method annotation must not be null."); } String[] exceptions = null; if (superMethodNode.getExceptions() != null) { exceptions = superMethodNode.getExceptions().toArray(new String[superMethodNode.getExceptions().size()]); } final ASMMethodNodeAdapter methodNode = new ASMMethodNodeAdapter(getInternalName(), new MethodNode(superMethodNode.getAccess(), superMethodNode.getName(), superMethodNode.getDesc(), superMethodNode.getSignature(), exceptions)); methodNode.addDelegator(superMethodNode.getDeclaringClassInternalName()); if (this.classNode.methods == null) { this.classNode.methods = new ArrayList<MethodNode>(); } this.classNode.methods.add(methodNode.getMethodNode()); return methodNode; } public void addGetterMethod(final String methodName, final ASMFieldNodeAdapter fieldNode) { if (methodName == null || fieldNode == null) { throw new IllegalArgumentException("method name or fieldNode annotation must not be null."); } // no argument is (). final String desc = "()" + fieldNode.getDesc(); final MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, methodName, desc, null, null); if (methodNode.instructions == null) { methodNode.instructions = new InsnList(); } final InsnList instructions = methodNode.instructions; // load this. instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // get fieldNode. instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldNode.getName(), fieldNode.getDesc())); // return of type. final Type type = Type.getType(fieldNode.getDesc()); instructions.add(new InsnNode(type.getOpcode(Opcodes.IRETURN))); if (this.classNode.methods == null) { this.classNode.methods = new ArrayList<MethodNode>(); } this.classNode.methods.add(methodNode); } public void addSetterMethod(final String methodName, final ASMFieldNodeAdapter fieldNode) { if (methodName == null || fieldNode == null) { throw new IllegalArgumentException("method name or fieldNode annotation must not be null."); } // void is V. final String desc = "(" + fieldNode.getDesc() + ")V"; final MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, methodName, desc, null, null); if (methodNode.instructions == null) { methodNode.instructions = new InsnList(); } final InsnList instructions = methodNode.instructions; // load this. instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); final Type type = Type.getType(fieldNode.getDesc()); // put field. instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), 1)); instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldNode.getName(), fieldNode.getDesc())); // return. instructions.add(new InsnNode(Opcodes.RETURN)); if (this.classNode.methods == null) { this.classNode.methods = new ArrayList<MethodNode>(); } this.classNode.methods.add(methodNode); } public void addInterface(final String interfaceName) { if (interfaceName == null) { throw new IllegalArgumentException("interface name must not be null."); } if (this.classNode.interfaces == null) { this.classNode.interfaces = new ArrayList<String>(); } this.classNode.interfaces.add(JavaAssistUtils.javaNameToJvmName(interfaceName)); } public void copyMethod(final ASMMethodNodeAdapter methodNode) { if (methodNode == null) { throw new IllegalArgumentException("method annotation must not be null"); } // change local call. final ASMMethodInsnNodeRemapper remapper = new ASMMethodInsnNodeRemapper(); remapper.addFilter(methodNode.getDeclaringClassInternalName(), null, null); remapper.setOwner(this.classNode.name); // remap method call. methodNode.remapMethodInsnNode(remapper); // remap desc of this. methodNode.remapLocalVariables("this", Type.getObjectType(this.classNode.name).getDescriptor()); if (this.classNode.methods == null) { this.classNode.methods = new ArrayList<MethodNode>(); } this.classNode.methods.add(methodNode.getMethodNode()); } public boolean hasAnnotation(final Class<?> annotationClass) { if (annotationClass == null) { return false; } final String desc = Type.getDescriptor(annotationClass); return hasAnnotation(desc, this.classNode.invisibleAnnotations) || hasAnnotation(desc, this.classNode.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 boolean subclassOf(final String classInternalName) { if (classInternalName == null) { return false; } if (classInternalName.equals("java/lang/Object")) { // super is root. return true; } ASMClassNodeAdapter classNode = this; while (classNode != null) { if (classInternalName.equals(classNode.getInternalName())) { return true; } final String superClassInternalName = classNode.getSuperClassInternalName(); if (superClassInternalName == null || superClassInternalName.equals("java/lang/Object")) { // find root annotation. return false; } // skip code. classNode = ASMClassNodeAdapter.get(this.pluginContext, this.classLoader, superClassInternalName, true); } return false; } public List<ASMClassNodeAdapter> getInnerClasses() { if (this.classNode.innerClasses == null) { return Collections.EMPTY_LIST; } final List<ASMClassNodeAdapter> innerClasses = new ArrayList<ASMClassNodeAdapter>(); final List<InnerClassNode> innerClassNodes = this.classNode.innerClasses; for (InnerClassNode node : innerClassNodes) { if (node.name == null) { continue; } // skip code. ASMClassNodeAdapter adapter = get(this.pluginContext, this.classLoader, node.name, true); if (adapter != null) { innerClasses.add(adapter); } } return innerClasses; } public byte[] toByteArray() { final int majorVersion = this.classNode.version & 0xFFFF; int flags = ClassWriter.COMPUTE_FRAMES; if (majorVersion <= 49) { // java 1.5 and less. flags = ClassWriter.COMPUTE_MAXS; } final ClassWriter classWriter = new ASMClassWriter(this.pluginContext, this.classNode.name, this.classNode.superName, flags, this.classLoader); this.classNode.accept(classWriter); return classWriter.toByteArray(); } }