com.codename1.tools.ikvm.Parser.java Source code

Java tutorial

Introduction

Here is the source code for com.codename1.tools.ikvm.Parser.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.codename1.tools.ikvm;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.util.CheckClassAdapter;

/**
 *
 * @author shannah
 */
public class Parser {
    //ClassNode classNode;
    static boolean changed = false;
    static boolean verify = Boolean.valueOf(System.getProperty("verify", "false"));

    /**
     * Parses an input stream with a class file, and writes the transformed class
     * to the output stream.  If no transformation occurs, it will simply copy 
     * the unchanged class to the output stream.
     * @param input The input stream containing a class.
     * @param output An output stream to write the transformed class to.
     * @throws Exception 
     */
    public static void parse(InputStream input, OutputStream output, ClassLoader classLoader) throws Exception {
        output.write(parse(input, classLoader));

    }

    /**
     * Parses an InputStream containing a class.
     * @param input The input stream with a class.
     * @return If a transformation occurred, the bytes for the changed class will be returned.  Otherwise null will be returned.
     * @throws Exception 
     */
    public static byte[] parse(InputStream input, ClassLoader classLoader) throws Exception {
        ClassReader r = new ClassReader(input);
        Parser p = new Parser();
        //ClassWriter w = new ClassWriter(r, 0);
        ClassNode classNode = new ClassNode();
        //p.classNode = classNode;

        r.accept(classNode, 0);
        //r.accept(p, ClassReader.EXPAND_FRAMES)

        List<MethodNode> methodsToAdd = new ArrayList<MethodNode>();
        int methodNum = 0;
        for (Object o : classNode.methods) {
            methodNum++;
            MethodNode methodNode = (MethodNode) o;
            boolean synchronizedMethod = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) == Opcodes.ACC_SYNCHRONIZED;
            if (synchronizedMethod) {
                // Check for a try statement
                final boolean[] tryCatchFound = new boolean[1];
                //System.out.println("Found sync method "+methodNode.name+". Checking for try blocks");
                methodNode.accept(new MethodVisitor(Opcodes.ASM5) {

                    @Override
                    public void visitTryCatchBlock(Label label, Label label1, Label label2, String string) {
                        tryCatchFound[0] = true;
                    }

                });
                if (!tryCatchFound[0]) {
                    continue;
                }

                //System.out.println("Instructions: "+Arrays.toString(methodNode.instructions.toArray()));

                System.out.println("Transforming method " + methodNode.name + " of class " + classNode.name);
                MethodDescriptor md = new MethodDescriptor(methodNode.access, methodNode.name, methodNode.desc);
                //methodNode.access = methodNode.access & ~Opcodes.ACC_SYNCHRONIZED;
                String privateMethodName = (md.constructor ? "___cn1init__" : methodNode.name) + "___cn1sync"
                        + (methodNum);
                MethodNode syncMethod = new MethodNode(methodNode.access, methodNode.name, methodNode.desc,
                        methodNode.signature,
                        (String[]) methodNode.exceptions.toArray(new String[methodNode.exceptions.size()]));

                methodNode.name = privateMethodName;
                methodNode.access = (methodNode.access | Opcodes.ACC_PRIVATE) & ~Opcodes.ACC_PUBLIC
                        & ~Opcodes.ACC_PROTECTED & ~Opcodes.ACC_SYNCHRONIZED;
                LabelNode startLabel = new LabelNode();
                syncMethod.instructions.add(startLabel);
                LabelNode endLabel = new LabelNode();

                int argIndex = 0;
                if (!md.staticMethod) {
                    //System.out.println(methodNode.name + " is not static");
                    syncMethod.localVariables.add(new LocalVariableNode("arg" + (argIndex),
                            "L" + classNode.name + ";", null, startLabel, endLabel, argIndex));
                    syncMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, argIndex++));

                }

                for (ByteCodeMethodArg arg : md.arguments) {
                    char typeChar = arg.type;
                    if (arg.dim > 0) {
                        typeChar = 'L';
                    }
                    if (arg.desc == null || arg.desc.isEmpty()) {
                        throw new RuntimeException(
                                "Invalid arg description for arg " + argIndex + " of method " + methodNode.name);
                    }
                    syncMethod.localVariables.add(new LocalVariableNode("arg" + (argIndex), arg.desc, arg.desc,
                            startLabel, endLabel, argIndex));

                    switch (typeChar) {
                    case 'L':
                        syncMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, argIndex++));
                        //syncMethod.localVariables.add(new LocalVariableNode("arg"+(argIndex-1), arg.desc, null, startLabel, endLabel, argIndex-1));
                        break;
                    case 'S':
                    case 'I':
                    case 'B':
                    case 'Z':
                    case 'C':
                        syncMethod.instructions.add(new VarInsnNode(Opcodes.ILOAD, argIndex++));
                        break;
                    case 'J':
                        syncMethod.instructions.add(new VarInsnNode(Opcodes.LLOAD, argIndex++));
                        argIndex++; // arg index increments 2 for double size args
                        break;
                    case 'F':
                        syncMethod.instructions.add(new VarInsnNode(Opcodes.FLOAD, argIndex++));
                        break;
                    case 'D':
                        syncMethod.instructions.add(new VarInsnNode(Opcodes.DLOAD, argIndex++));
                        argIndex++;// arg index increments 2 for double size args
                        break;
                    default:
                        throw new IllegalArgumentException("Unsupported argument type " + arg.type);
                    }
                }

                if (md.staticMethod) {
                    syncMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, classNode.name,
                            privateMethodName, methodNode.desc));
                } else {
                    syncMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.name,
                            privateMethodName, methodNode.desc));
                }

                if (md.returnType != null) {
                    char typeChar = md.returnType.type;
                    if (md.returnType.dim > 0) {
                        typeChar = 'L';
                    }
                    switch (typeChar) {
                    case 'L':
                        syncMethod.instructions.add(new InsnNode(Opcodes.ARETURN));
                        break;
                    case 'S':
                    case 'I':
                    case 'B':
                    case 'Z':
                    case 'C':
                        syncMethod.instructions.add(new InsnNode(Opcodes.IRETURN));
                        break;
                    case 'J':
                        syncMethod.instructions.add(new InsnNode(Opcodes.LRETURN));
                        break;
                    case 'F':
                        syncMethod.instructions.add(new InsnNode(Opcodes.FRETURN));
                        break;
                    case 'D':
                        syncMethod.instructions.add(new InsnNode(Opcodes.DRETURN));
                        break;
                    case 'V':
                        syncMethod.instructions.add(new InsnNode(Opcodes.RETURN));
                        break;
                    default:
                        throw new IllegalArgumentException("Unsupported argument type " + md.returnType.type);
                    }
                } else {
                    syncMethod.instructions.add(new InsnNode(Opcodes.DRETURN));
                }

                syncMethod.instructions.add(endLabel);

                methodsToAdd.add(syncMethod);

            }
        }
        if (!methodsToAdd.isEmpty()) {
            changed = true;
            System.out
                    .println("Transforming " + methodsToAdd.size() + " synchronized methods in " + classNode.name);
            classNode.methods.addAll(methodsToAdd);
            ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            classNode.accept(w);
            byte[] out = w.toByteArray();
            if (verify) {
                verify(out, classLoader);
            }
            return out;
        } else {
            ClassWriter w = new ClassWriter(0);
            classNode.accept(w);
            byte[] out = w.toByteArray();
            return out;
        }

    }

    /**
     * Parse a .class file. If changes are required, it will overwrite the same
     * file with the changed file contents.
     * @param sourceFile A .class file to transform.
     * @throws Exception 
     */
    public static void parse(File sourceFile, ClassLoader classLoader) throws Exception {
        changed = false;
        byte[] newBytes = parse(new FileInputStream(sourceFile), classLoader);
        if (changed) {
            try (FileOutputStream fos = new FileOutputStream(sourceFile)) {
                fos.write(newBytes);
            }
        }
    }

    public static class VerifyException extends Exception {
        public VerifyException(String msg) {
            super(msg);
        }
    }

    public static void verify(byte[] clazz, ClassLoader classLoader) throws VerifyException {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        CheckClassAdapter.verify(new ClassReader(clazz), classLoader, false, pw);
        if (sw.toString().length() > 0) {
            throw new VerifyException(sw.toString());
        }
    }

    /*
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
    boolean synchronizedMethod = (access & Opcodes.ACC_SYNCHRONIZED) == Opcodes.ACC_SYNCHRONIZED;
    if (synchronizedMethod) {
        System.out.println("Found Synchronized Method: name="+name+", desc="+desc+", signature="+signature+", execeptions="+Arrays.toString(exceptions));
    }
    //BytecodeMethod mtd = new BytecodeMethod(clsName, access, name, desc, signature, exceptions);
    //cls.addMethod(mtd);
    //JSRInlinerAdapter a = new JSRInlinerAdapter(new MethodVisitorWrapper(super.visitMethod(access, name, desc, signature, exceptions), mtd), access, name, desc, signature, exceptions);
    return super.visitMethod(access, name, desc, signature, exceptions);
    }*/

    private static class MethodDescriptor {
        List<ByteCodeMethodArg> arguments = new ArrayList<ByteCodeMethodArg>();
        ByteCodeMethodArg returnType;
        boolean constructor;
        boolean staticMethod;

        private MethodDescriptor(int access, String methodName, String desc) {
            staticMethod = (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC;
            int pos = desc.lastIndexOf(')');
            if (methodName.equals("<init>")) {
                methodName = "__INIT__";
                constructor = true;
                returnType = new ByteCodeMethodArg('V', 0, "V");
            } else {
                if (methodName.equals("<clinit>")) {
                    methodName = "__CLINIT__";
                    returnType = new ByteCodeMethodArg('V', 0, "V");
                    staticMethod = true;
                } else {
                    String retType = desc.substring(pos + 1);
                    if (retType.equals("V")) {
                        returnType = new ByteCodeMethodArg('V', 0, "V");
                    } else {
                        int dim = 0;
                        while (retType.startsWith("[")) {
                            retType = retType.substring(1);

                            dim++;
                        }
                        char currentType = retType.charAt(0);
                        String objectType = retType;
                        switch (currentType) {
                        case 'L':
                            int idx = retType.indexOf(';');
                            objectType = retType.substring(1, idx);
                            returnType = new ByteCodeMethodArg(objectType, dim);
                            break;
                        case 'I':

                        case 'J':

                        case 'B':

                        case 'S':

                        case 'F':

                        case 'D':

                        case 'Z':

                        case 'C':
                            returnType = new ByteCodeMethodArg(currentType, dim, "" + currentType);
                            break;
                        }
                    }
                }
            }

            int currentArrayDim = 0;
            desc = desc.substring(1, pos);
            for (int i = 0; i < desc.length(); i++) {
                char currentType = desc.charAt(i);
                switch (currentType) {
                case '[':
                    // array of...
                    currentArrayDim++;
                    continue;
                case 'L':
                    int idx = desc.indexOf(';', i);
                    String objectType = desc.substring(i, idx);
                    i = idx;
                    arguments.add(new ByteCodeMethodArg(objectType, currentArrayDim));
                    break;
                case 'I':

                case 'J':

                case 'B':

                case 'S':

                case 'F':

                case 'D':

                case 'Z':

                case 'C':
                    arguments.add(new ByteCodeMethodArg(currentType, currentArrayDim, "" + currentType));
                    break;
                }
                currentArrayDim = 0;
            }

        }
    }

    private static class ByteCodeMethodArg {
        boolean isPrimitive;
        int dim;
        char type;
        String desc;

        private ByteCodeMethodArg(char type, int dim, String desc) {
            this.type = type;
            this.dim = dim;
            this.desc = desc;
        }

        private ByteCodeMethodArg(String type, int dim) {
            this.type = 'L';
            this.dim = dim;
            this.desc = type + (type.endsWith(";") ? "" : ";");
        }
    }

    /**
     * Copy the input stream into the output stream, closes both streams when
     * finishing or in a case of an exception
     *
     * @param i source
     * @param o destination
     * @param bufferSize the size of the buffer, which should be a power of 2
     * large enoguh
     */
    public static void copy(InputStream i, OutputStream o, int bufferSize) throws IOException {

        byte[] buffer = new byte[bufferSize];
        int size = i.read(buffer);
        while (size > -1) {
            o.write(buffer, 0, size);
            size = i.read(buffer);
        }

    }

    /**
     * Closes the object (connection, stream etc.) without throwing any
     * exception, even if the object is null
     *
     * @param o Connection, Stream or other closeable object
     */
    public static void cleanup(Object o) {
        try {
            if (o instanceof OutputStream) {
                ((OutputStream) o).close();
                return;
            }
            if (o instanceof InputStream) {
                ((InputStream) o).close();
                return;
            }
        } catch (IOException err) {
            err.printStackTrace();
        }
    }

}