com.geeksaga.light.profiler.instrument.transformer.MethodParameterTransformer.java Source code

Java tutorial

Introduction

Here is the source code for com.geeksaga.light.profiler.instrument.transformer.MethodParameterTransformer.java

Source

/*
 * Copyright 2015 GeekSaga.
 *
 * 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.geeksaga.light.profiler.instrument.transformer;

import com.geeksaga.light.agent.TraceContext;
import com.geeksaga.light.agent.core.TraceRegisterBinder;
import com.geeksaga.light.agent.trace.DebugTrace;
import com.geeksaga.light.agent.trace.MethodTrace;
import com.geeksaga.light.agent.trace.Parameter;
import com.geeksaga.light.logger.CommonLogger;
import com.geeksaga.light.logger.LightLogger;
import com.geeksaga.light.profiler.asm.ClassNodeWrapper;
import com.geeksaga.light.profiler.asm.ClassReaderWrapper;
import com.geeksaga.light.profiler.filter.Filter;
import com.geeksaga.light.profiler.filter.LightFilter;
import com.geeksaga.light.profiler.util.ASMUtil;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.LocalVariablesSorter;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import static com.geeksaga.light.profiler.util.ASMUtil.getInternalName;
import static org.objectweb.asm.Opcodes.*;

/**
 * The type Method parameter transformer.
 *
 * @author geeksaga
 */
public class MethodParameterTransformer implements ClassFileTransformer {
    private LightLogger logger;

    private Filter filter;

    private TraceRegisterBinder traceRegisterBinder;
    private TraceContext traceContext;
    private int traceId;

    /**
     * The constant WINDOWS_OS.
     */
    public static final boolean WINDOWS_OS = getSystemProperty("os.name", "unix").contains("Window");

    /**
     * Instantiates a new Method parameter transformer.
     *
     * @param traceRegisterBinder the trace register binder
     * @param traceContext        the trace context
     */
    public MethodParameterTransformer(TraceRegisterBinder traceRegisterBinder, TraceContext traceContext) {
        this.logger = CommonLogger.getLogger(getClass().getName());
        this.filter = new LightFilter(traceContext);

        this.traceRegisterBinder = traceRegisterBinder;
        this.traceContext = traceContext;
        this.traceId = this.traceRegisterBinder.getTraceRegistryAdaptor().add(new MethodTrace(traceContext));
    }

    private static String getSystemProperty(String key, String def) {
        try {
            return System.getProperty(key, def);
        } catch (RuntimeException exception) {
            return def;
        }
    }

    @Override
    public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if (filter.allow(classLoader, className)) {
            logger.info("Transform => " + className);

            ClassNodeWrapper classNodeWrapper = new ClassNodeWrapper();
            ClassReader reader = new ClassReaderWrapper(classfileBuffer);
            reader.accept(new ClassVisitor(Opcodes.ASM5, classNodeWrapper) {
                @Override
                public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                        String[] exceptions) {
                    MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);

                    if (name.contains("<")) {
                        return methodVisitor;
                    }

                    return new MethodParameterVisitor(access, desc, methodVisitor, ASMUtil.isStatic(access));
                }
            }, ClassReader.EXPAND_FRAMES);

            if (classNodeWrapper.isInterface()) {
                return classfileBuffer;
            }

            byte[] bytes = ASMUtil.toBytes(classNodeWrapper);

            save(System.getProperty("user.dir") + File.separator + "Main.class", bytes);

            return bytes;
        }

        return classfileBuffer;
    }

    /**
     * Save.
     *
     * @param name the name
     * @param buff the buff
     */
    public void save(String name, byte[] buff) {
        try {
            BufferedOutputStream out = new BufferedOutputStream(
                    new FileOutputStream(new File(replaceWindowsSeparator(name))));
            out.write(buff);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Replace windows separator string.
     *
     * @param path the path
     * @return the string
     */
    public String replaceWindowsSeparator(String path) {
        if (WINDOWS_OS) {
            if (path != null) {
                return path.replace("\\", "\\\\");
            }
        }

        return path;
    }

    /**
     * The type Method parameter visitor.
     */
    class MethodParameterVisitor extends LocalVariablesSorter {
        /**
         * The Argument class internal name.
         */
        public final String ARGUMENT_CLASS_INTERNAL_NAME = getInternalName(Parameter.class.getName());

        private String desc;
        private boolean isStatic = false;
        private int[] parameterIndices;

        /**
         * Instantiates a new Method parameter visitor.
         *
         * @param access        the access
         * @param desc          the desc
         * @param methodVisitor the method visitor
         * @param isStatic      the is static
         */
        public MethodParameterVisitor(int access, String desc, MethodVisitor methodVisitor, boolean isStatic) {
            super(Opcodes.ASM5, access, desc, methodVisitor);

            this.desc = desc;
            this.isStatic = isStatic;
            this.parameterIndices = ASMUtil.getFixedArgumentIndices(desc, isStatic);
        }

        @Override
        public void visitCode() {
            int parameterVariableIndex = newLocal(Type.getType(ARGUMENT_CLASS_INTERNAL_NAME));
            Type[] argumentTypes = Type.getArgumentTypes(desc);

            mv.visitTypeInsn(NEW, ARGUMENT_CLASS_INTERNAL_NAME);
            mv.visitInsn(DUP);
            mv.visitIntInsn(BIPUSH, isStatic ? argumentTypes.length : argumentTypes.length + 1); // separate type
            mv.visitMethodInsn(INVOKESPECIAL, ARGUMENT_CLASS_INTERNAL_NAME, "<init>",
                    Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false);
            mv.visitVarInsn(ASTORE, parameterVariableIndex);

            int parameterIndex = 0;
            if (!isStatic) {
                mv.visitVarInsn(ALOAD, parameterVariableIndex);
                mv.visitInsn(DUP);
                mv.visitIntInsn(BIPUSH, parameterIndex);
                mv.visitVarInsn(ALOAD, parameterIndices[parameterIndex]);
                mv.visitMethodInsn(INVOKEVIRTUAL, ARGUMENT_CLASS_INTERNAL_NAME, "set", "(ILjava/lang/Object;)V",
                        false);

                parameterIndex++;
            }

            for (int i = 0, j = isStatic ? 0 : 1; i < argumentTypes.length; i++, j++) {
                Type type = argumentTypes[i];

                switch (type.getSort()) {
                case Type.BOOLEAN:
                case Type.CHAR:
                case Type.BYTE:
                case Type.SHORT:
                case Type.INT:
                    visitInstruction(ILOAD, j, parameterVariableIndex, parameterIndex);

                    String description = null;
                    switch (type.getSort()) {
                    case Type.BOOLEAN:
                        description = "(IZ)V";
                        break;
                    case Type.CHAR:
                        description = "(IC)V";
                        break;
                    case Type.BYTE:
                        description = "(IB)V";
                        break;
                    case Type.SHORT:
                        description = "(IS)V";
                        break;
                    case Type.INT:
                        description = "(II)V";
                        break;
                    }

                    mv.visitMethodInsn(INVOKEVIRTUAL, ARGUMENT_CLASS_INTERNAL_NAME, "set", description, false);

                    break;
                case Type.FLOAT:
                    visitInstruction(FLOAD, j, parameterVariableIndex, parameterIndex);

                    mv.visitMethodInsn(INVOKEVIRTUAL, ARGUMENT_CLASS_INTERNAL_NAME, "set", "(IF)V", false);

                    break;
                case Type.LONG:
                    visitInstruction(LLOAD, j, parameterVariableIndex, parameterIndex);

                    mv.visitMethodInsn(INVOKEVIRTUAL, ARGUMENT_CLASS_INTERNAL_NAME, "set", "(IJ)V", false);

                    break;
                case Type.DOUBLE:
                    visitInstruction(DLOAD, j, parameterVariableIndex, parameterIndex);

                    mv.visitMethodInsn(INVOKEVIRTUAL, ARGUMENT_CLASS_INTERNAL_NAME, "set", "(ID)V", false);

                    break;
                case Type.ARRAY:
                case Type.OBJECT:
                    visitInstruction(ALOAD, j, parameterVariableIndex, parameterIndex);

                    mv.visitMethodInsn(INVOKEVIRTUAL, ARGUMENT_CLASS_INTERNAL_NAME, "set", "(ILjava/lang/Object;)V",
                            false);

                    break;
                default:
                    throw new IllegalAccessError("Unknown type. " + type);
                }

                parameterIndex++;
            }

            mv.visitVarInsn(ALOAD, parameterVariableIndex);
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, getInternalName(DebugTrace.class.getName()), "traceParameter",
                    "(L" + getInternalName(Parameter.class.getName()) + ";)V", false);
            mv.visitCode();
        }

        private void visitInstruction(int opcode, int index, int parameterVariableIndex, int parameterIndex) {
            mv.visitVarInsn(ALOAD, parameterVariableIndex);
            mv.visitLdcInsn(index);
            mv.visitVarInsn(opcode, parameterIndices[parameterIndex]);
        }

        public void visitMaxs(int maxStack, int maxLocals) {
            mv.visitMaxs(maxStack, maxLocals);
        }
    }
}