io.awacs.plugin.stacktrace.ClassTransformer.java Source code

Java tutorial

Introduction

Here is the source code for io.awacs.plugin.stacktrace.ClassTransformer.java

Source

/**
 * Copyright 2016 AWACS Project.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 io.awacs.plugin.stacktrace;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Java Bytecode transformer using asm.
 * <p>
 * <p>
 * Created by pixyonly on 16/10/24.
 */
abstract class ClassTransformer {

    //
    protected abstract boolean filterMethod(ClassNode cn, MethodNode mn);

    //??
    protected abstract boolean isTerminatedMethod(MethodNode mn);

    //?
    public void visit(ClassNode cn) {
        cn.check(Opcodes.ASM5);
        //??
        List<MethodNode> appended = new ArrayList<>(cn.methods.size());
        //??
        for (Object mn : cn.methods) {
            MethodNode src = (MethodNode) mn;
            //
            if (!filterMethod(cn, src)) {
                continue;
            }
            boolean terminated = isTerminatedMethod(src);

            if (terminated) {
                //copy exceptions
                String[] exceptions = null;
                if (src.exceptions != null) {
                    exceptions = new String[src.exceptions.size()];
                    for (int i = 0; i < src.exceptions.size(); i++) {
                        exceptions[i] = src.exceptions.get(i).toString();
                    }
                }
                //declare method
                MethodNode proxy = new MethodNode(src.access, src.name, src.desc, src.signature, exceptions);
                appended.add(proxy);
                //copy method annotations
                List<AnnotationNode> methodAnns = null;
                if (src.visibleAnnotations != null) {
                    methodAnns = new ArrayList<>(src.visibleAnnotations.size());
                    methodAnns.addAll(src.visibleAnnotations);
                }
                proxy.visibleAnnotations = methodAnns;
                //copy parameter annotations
                List[] parameterAnns = null;
                if (src.visibleParameterAnnotations != null) {
                    parameterAnns = new List[src.visibleParameterAnnotations.length];
                    System.arraycopy(src.visibleParameterAnnotations, 0, parameterAnns, 0,
                            src.visibleParameterAnnotations.length);
                }
                proxy.visibleParameterAnnotations = parameterAnns;
                //clear origin method's annotation and change name
                int _slash = cn.name.lastIndexOf('/');
                //??
                src.name = src.name + "_origin_" + cn.name.substring(_slash + 1);
                src.access = src.access & ~Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE;
                src.visibleAnnotations = null;
                src.visibleLocalVariableAnnotations = null;
                transformTerminatedMethod(src, proxy, cn);
            } else {
                transformPlainMethod(src, cn);
            }
        }
        cn.methods.addAll(appended);
    }

    //?
    private static boolean isPrimitive(char c) {
        return c == 'J' || c == 'D' || c == 'F' || c == 'I' || c == 'S' || c == 'C' || c == 'B' || c == 'Z';
    }

    //??????
    private static List<String> resolveParameters(String descriptor) {
        String desc = descriptor.substring(1, descriptor.indexOf(')'));
        List<String> params = new LinkedList<>();
        for (int i = 0; i < desc.length(); i++) {
            int tag = 0;
            switch (desc.charAt(i)) {
            case 'L':
                tag = expectType(desc, i);
                params.add(tag + 1 == desc.length() ? desc.substring(i) : desc.substring(i, tag + 1));
                i = tag;
                break;
            case '[':
                tag = expectAny(desc, i);
                params.add(tag + 1 == desc.length() ? desc.substring(i) : desc.substring(i, tag + 1));
                i = tag;
                break;
            default:
                if (!isPrimitive(desc.charAt(i)))
                    throw new IllegalDescriptorException(desc);
                params.add(String.valueOf(desc.charAt(i)));
            }
        }
        return params;
    }

    //?????
    private static int expectType(String s, int offset) {
        while (s.charAt(offset) != ';')
            offset++;
        return offset;
    }

    //??????
    private static int expectAny(String s, int offset) {
        if (s.charAt(offset + 1) == '[')
            return expectAny(s, offset + 1);
        else if (s.charAt(offset + 1) == 'L')
            return expectType(s, offset + 1);
        else if (isPrimitive(s.charAt(offset + 1)))
            return offset + 1;
        else
            throw new IllegalDescriptorException(s);
    }

    protected boolean isMainMethod(MethodNode mn) {
        return mn.name.equals("main") && mn.desc.equals("([Ljava/lang/String;)V")
                && (mn.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC;
    }

    protected boolean isConstructor(MethodNode mn) {
        return mn.name.equals("<init>") || mn.name.equals("<clinit>");
    }

    /**
     * ?
     * 1?try catch?                 try{
     * 2????        io.awacs.plugin.stacktrace.StackFrames.init();
     * 3????                io.awacs.plugin.stacktrace.StackFrames.push(className,methodName,0);
     * 4?                          Object val = methodName_origin_className(args);
     * 5?????                io.awacs.plugin.stacktrace.StackFrames.push(className,methodName,1);
     * 5???          List list = io.awacs.plugin.stacktrace.StackFrames.dump();
     * 7?????          io.awacs.plugin.stacktrace.StackTracePlugin.incrAccess(list);
     * 8?                          return val;
     * }catch(java.lang.Exception e){
     * 9??                io.awacs.plugin.stacktrace.StackTracePlugin.incrFailure(e);
     * 10?               throw e;
     * }
     */
    private void transformTerminatedMethod(MethodNode origin, MethodNode proxy, ClassNode owner) {
        LabelNode l0 = new LabelNode();
        LabelNode l1 = new LabelNode();
        LabelNode l2 = new LabelNode();
        //try catch?
        proxy.tryCatchBlocks.add(new TryCatchBlockNode(l0, l1, l2, "java/lang/Exception"));
        proxy.instructions.add(l0);
        //?
        proxy.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/awacs/plugin/stacktrace/StackFrames",
                "init", "()V", false));
        proxy.instructions.add(new LdcInsnNode(owner.name.replaceAll("/", ".")));
        proxy.instructions.add(new LdcInsnNode(proxy.name));
        proxy.instructions.add(new LdcInsnNode(0));
        //
        proxy.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/awacs/plugin/stacktrace/StackFrames",
                "push", "(Ljava/lang/String;Ljava/lang/String;I)V", false));
        int varIndex = 0;//???
        //???,????this?
        if ((proxy.access & Opcodes.ACC_STATIC) != Opcodes.ACC_STATIC) {
            proxy.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            varIndex = 1;
        }
        List<String> parameters = resolveParameters(proxy.desc);
        //???
        for (String param : parameters) {
            VarInsnNode insnNode;
            switch (param) {
            case "J":
                insnNode = new VarInsnNode(Opcodes.LLOAD, varIndex);
                varIndex += 2;
                break;
            case "D":
                insnNode = new VarInsnNode(Opcodes.DLOAD, varIndex);
                varIndex += 2;
                break;
            case "F":
                insnNode = new VarInsnNode(Opcodes.FLOAD, varIndex++);
                break;
            case "I":
                insnNode = new VarInsnNode(Opcodes.ILOAD, varIndex++);
                break;
            case "S":
                insnNode = new VarInsnNode(Opcodes.ILOAD, varIndex++);
                break;
            case "Z":
                insnNode = new VarInsnNode(Opcodes.ILOAD, varIndex++);
                break;
            case "B":
                insnNode = new VarInsnNode(Opcodes.ILOAD, varIndex++);
                break;
            case "C":
                insnNode = new VarInsnNode(Opcodes.ILOAD, varIndex++);
                break;
            default:
                insnNode = new VarInsnNode(Opcodes.ALOAD, varIndex++);
                break;
            }
            proxy.instructions.add(insnNode);
        }
        //
        if ((origin.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC)
            proxy.instructions
                    .add(new MethodInsnNode(Opcodes.INVOKESTATIC, owner.name, origin.name, origin.desc, false));
        else
            proxy.instructions
                    .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, owner.name, origin.name, origin.desc, false));
        proxy.instructions.add(new LdcInsnNode(owner.name.replaceAll("/", ".")));
        proxy.instructions.add(new LdcInsnNode(proxy.name));
        proxy.instructions.add(new LdcInsnNode(1));
        //?
        proxy.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/awacs/plugin/stacktrace/StackFrames",
                "push", "(Ljava/lang/String;Ljava/lang/String;I)V", false));
        //??
        proxy.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/awacs/plugin/stacktrace/StackFrames",
                "dump", "()Ljava/util/List;", false));
        //???
        proxy.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                "io/awacs/plugin/stacktrace/StackTracePlugin", "incrAccess", "(Ljava/util/List;)V", false));
        proxy.instructions.add(l1);
        //
        String returnType = origin.desc.substring(origin.desc.indexOf(')') + 1);
        switch (returnType) {
        case "J":
            proxy.instructions.add(new InsnNode(Opcodes.LRETURN));
            break;
        case "D":
            proxy.instructions.add(new InsnNode(Opcodes.DRETURN));
            break;
        case "F":
            proxy.instructions.add(new InsnNode(Opcodes.FRETURN));
            break;
        case "I":
            proxy.instructions.add(new InsnNode(Opcodes.IRETURN));
            break;
        case "S":
            proxy.instructions.add(new InsnNode(Opcodes.IRETURN));
            break;
        case "C":
            proxy.instructions.add(new InsnNode(Opcodes.IRETURN));
            break;
        case "B":
            proxy.instructions.add(new InsnNode(Opcodes.IRETURN));
            break;
        case "Z":
            proxy.instructions.add(new InsnNode(Opcodes.IRETURN));
            break;
        case "V":
            proxy.instructions.add(new InsnNode(Opcodes.RETURN));
            break;
        default:
            proxy.instructions.add(new InsnNode(Opcodes.ARETURN));
            break;
        }
        proxy.instructions.add(l2);
        //?
        proxy.instructions.add(new FrameNode(Opcodes.F_SAME1, 0, null, 1, new Object[] { "java/lang/Exception" }));
        proxy.instructions.add(new VarInsnNode(Opcodes.ASTORE, varIndex));
        proxy.instructions.add(new VarInsnNode(Opcodes.ALOAD, varIndex));
        proxy.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                "io/awacs/plugin/stacktrace/StackTracePlugin", "incrFailure", "(Ljava/lang/Throwable;)V", false));
        proxy.instructions.add(new VarInsnNode(Opcodes.ALOAD, varIndex));
        proxy.instructions.add(new InsnNode(Opcodes.ATHROW));
        proxy.maxLocals = varIndex + 1;
        proxy.maxStack = Math.max(varIndex, 5);
    }

    /**
     * ??
     * 1?StackFrames.push(0)???
     * 2???this???
     * 3?StackFrames.push(1)???
     * 4??
     */
    private void transformPlainMethod(MethodNode mn, ClassNode cn) {
        InsnList before = new InsnList();
        before.add(new LdcInsnNode(cn.name.replaceAll("/", ".")));
        before.add(new LdcInsnNode(mn.name));
        before.add(new LdcInsnNode(0));
        before.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/awacs/plugin/stacktrace/StackFrames", "push",
                "(Ljava/lang/String;Ljava/lang/String;I)V", false));
        InsnList end = new InsnList();
        end.add(new LdcInsnNode(cn.name.replaceAll("/", ".")));
        end.add(new LdcInsnNode(mn.name));
        end.add(new LdcInsnNode(1));
        end.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/awacs/plugin/stacktrace/StackFrames", "push",
                "(Ljava/lang/String;Ljava/lang/String;I)V", false));

        List<AbstractInsnNode> insts = new LinkedList<>();
        for (int i = 0; i < mn.instructions.size(); i++) {
            int opcode = mn.instructions.get(i).getOpcode();
            if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
                insts.add(mn.instructions.get(i));
            }
        }
        if (!insts.isEmpty()) {
            mn.instructions.insert(before);
            for (AbstractInsnNode node : insts) {
                mn.instructions.insertBefore(node, end);
            }
        }
        mn.maxStack = mn.maxStack + 5;
    }

}