org.apache.commons.javaflow.bytecode.transformation.asm.ContinuationMethodAnalyzer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.commons.javaflow.bytecode.transformation.asm.ContinuationMethodAnalyzer.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.commons.javaflow.bytecode.transformation.asm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SimpleVerifier;
import org.objectweb.asm.tree.analysis.SourceInterpreter;
import org.objectweb.asm.tree.analysis.SourceValue;

public class ContinuationMethodAnalyzer extends MethodNode implements Opcodes {

    protected final String className;
    protected final ClassVisitor cv;
    protected final MethodVisitor mv;

    protected final List<Label> labels = new ArrayList<Label>();
    protected final List<MethodInsnNode> nodes = new ArrayList<MethodInsnNode>();
    protected final List<MethodInsnNode> methods = new ArrayList<MethodInsnNode>();

    protected Analyzer analyzer;
    public int stackRecorderVar;

    public ContinuationMethodAnalyzer(String className, ClassVisitor cv, MethodVisitor mv, int access, String name,
            String desc, String signature, String[] exceptions) {
        super(Opcodes.ASM4, access, name, desc, signature, exceptions);
        this.className = className;
        this.cv = cv;
        this.mv = mv;
    }

    public int getIndex(AbstractInsnNode node) {
        return instructions.indexOf(node);
    }

    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
        MethodInsnNode mnode = new MethodInsnNode(opcode, owner, name, desc);
        if (opcode == INVOKESPECIAL || "<init>".equals(name)) {
            methods.add(mnode);
        }
        if (needsFrameGuard(opcode, owner, name, desc) /* && transformer.inScope(owner, name)*/) {
            Label label = new Label();
            super.visitLabel(label);
            labels.add(label);
            nodes.add(mnode);
        }
        instructions.add(mnode);
    }

    public void visitEnd() {
        if (instructions.size() == 0 || labels.size() == 0) {
            accept(mv);
            return;
        }

        /*
        {
              TraceMethodVisitor mv = new TraceMethodVisitor();
              System.err.println(name + desc);
              for (int j = 0; j < instructions.size(); ++j) {
                  ((AbstractInsnNode) instructions.get(j)).accept(mv);
                  System.err.print("   " + mv.text.get(j)); // mv.text.get(j));
              }
              System.err.println();
        }
        */

        this.stackRecorderVar = maxLocals;
        try {
            moveNew();

            // analyzer = new Analyzer(new BasicVerifier());
            analyzer = new Analyzer(new SimpleVerifier() {

                protected Class<?> getClass(Type t) {
                    try {
                        if (t.getSort() == Type.ARRAY) {
                            return Class.forName(t.getDescriptor().replace('/', '.'), true,
                                    Thread.currentThread().getContextClassLoader());
                        }
                        return Class.forName(t.getClassName(), true,
                                Thread.currentThread().getContextClassLoader());
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e.toString());
                    }
                }
            }) {

                protected Frame newFrame(final int nLocals, final int nStack) {
                    return new MonitoringFrame(nLocals, nStack);
                }

                protected Frame newFrame(final Frame src) {
                    return new MonitoringFrame(src);
                }

                public Frame[] analyze(final String owner, final MethodNode m) throws AnalyzerException {
                    // System.out.println("Analyze: "+owner+"|"+m.name+"|"+m.signature+"|"+m.tryCatchBlocks);
                    final Frame[] frames = super.analyze(owner, m);
                    for (int i = 0; i < m.instructions.size(); i++) {
                        int opcode = m.instructions.get(i).getOpcode();
                        if (opcode == MONITORENTER || opcode == MONITOREXIT) {
                            // System.out.println(i);
                        }
                    }
                    return frames;
                }
            };

            analyzer.analyze(className, this);
            accept(new ContinuationMethodAdapter(this));

            /*
            {
                  TraceMethodVisitor mv = new TraceMethodVisitor();
                  System.err.println("=================");
                
                  System.err.println(name + desc);
                  for (int j = 0; j < instructions.size(); ++j) {
                      ((AbstractInsnNode) instructions.get(j)).accept(mv);
                      System.err.print("   " + mv.text.get(j)); // mv.text.get(j));
                  }
                  System.err.println();
            }
            */

        } catch (AnalyzerException ex) {
            // TODO log the error or fail?
            ex.printStackTrace();
            accept(mv);

        }
    }

    @SuppressWarnings("unchecked")
    void moveNew() throws AnalyzerException {
        SourceInterpreter i = new SourceInterpreter();
        Analyzer a = new Analyzer(i);
        a.analyze(className, this);

        final HashMap<AbstractInsnNode, MethodInsnNode> movable = new HashMap<AbstractInsnNode, MethodInsnNode>();

        Frame[] frames = a.getFrames();
        for (int j = 0; j < methods.size(); j++) {
            MethodInsnNode mnode = methods.get(j);
            // require to move NEW instruction
            int n = instructions.indexOf(mnode);
            Frame f = frames[n];
            Type[] args = Type.getArgumentTypes(mnode.desc);

            SourceValue v = (SourceValue) f.getStack(f.getStackSize() - args.length - 1);
            Set<AbstractInsnNode> insns = v.insns;
            for (final AbstractInsnNode ins : insns) {
                if (ins.getOpcode() == NEW) {
                    movable.put(ins, mnode);
                } else {
                    // other known patterns
                    int n1 = instructions.indexOf(ins);
                    if (ins.getOpcode() == DUP) { // <init> with params
                        AbstractInsnNode ins1 = instructions.get(n1 - 1);
                        if (ins1.getOpcode() == NEW) {
                            movable.put(ins1, mnode);
                        }
                    } else if (ins.getOpcode() == SWAP) { // in exception handler
                        AbstractInsnNode ins1 = instructions.get(n1 - 1);
                        AbstractInsnNode ins2 = instructions.get(n1 - 2);
                        if (ins1.getOpcode() == DUP_X1 && ins2.getOpcode() == NEW) {
                            movable.put(ins2, mnode);
                        }
                    }
                }
            }
        }

        int updateMaxStack = 0;
        for (final Map.Entry<AbstractInsnNode, MethodInsnNode> e : movable.entrySet()) {
            AbstractInsnNode node1 = e.getKey();
            int n1 = instructions.indexOf(node1);
            AbstractInsnNode node2 = instructions.get(n1 + 1);
            AbstractInsnNode node3 = instructions.get(n1 + 2);
            int producer = node2.getOpcode();

            instructions.remove(node1); // NEW
            boolean requireDup = false;
            if (producer == DUP) {
                instructions.remove(node2); // DUP
                requireDup = true;
            } else if (producer == DUP_X1) {
                instructions.remove(node2); // DUP_X1
                instructions.remove(node3); // SWAP
                requireDup = true;
            }

            MethodInsnNode mnode = (MethodInsnNode) e.getValue();
            AbstractInsnNode nm = mnode;

            int varOffset = stackRecorderVar + 1;
            Type[] args = Type.getArgumentTypes(mnode.desc);

            // optimizations for some common cases
            if (args.length == 0) {
                final InsnList doNew = new InsnList();
                doNew.add(node1); // NEW
                if (requireDup)
                    doNew.add(new InsnNode(DUP));
                instructions.insertBefore(nm, doNew);
                nm = doNew.getLast();
                continue;
            }

            if (args.length == 1 && args[0].getSize() == 1) {
                final InsnList doNew = new InsnList();
                doNew.add(node1); // NEW
                if (requireDup) {
                    doNew.add(new InsnNode(DUP));
                    doNew.add(new InsnNode(DUP2_X1));
                    doNew.add(new InsnNode(POP2));
                    updateMaxStack = updateMaxStack < 2 ? 2 : updateMaxStack; // a two extra slots for temp values
                } else
                    doNew.add(new InsnNode(SWAP));
                instructions.insertBefore(nm, doNew);
                nm = doNew.getLast();
                continue;
            }

            // TODO this one untested!
            if ((args.length == 1 && args[0].getSize() == 2)
                    || (args.length == 2 && args[0].getSize() == 1 && args[1].getSize() == 1)) {
                final InsnList doNew = new InsnList();
                doNew.add(node1); // NEW
                if (requireDup) {
                    doNew.add(new InsnNode(DUP));
                    doNew.add(new InsnNode(DUP2_X2));
                    doNew.add(new InsnNode(POP2));
                    updateMaxStack = updateMaxStack < 2 ? 2 : updateMaxStack; // a two extra slots for temp values
                } else {
                    doNew.add(new InsnNode(DUP_X2));
                    doNew.add(new InsnNode(POP));
                    updateMaxStack = updateMaxStack < 1 ? 1 : updateMaxStack; // an extra slot for temp value
                }
                instructions.insertBefore(nm, doNew);
                nm = doNew.getLast();
                continue;
            }

            final InsnList doNew = new InsnList();
            // generic code using temporary locals
            // save stack
            for (int j = args.length - 1; j >= 0; j--) {
                Type type = args[j];

                doNew.add(new VarInsnNode(type.getOpcode(ISTORE), varOffset));
                varOffset += type.getSize();
            }
            if (varOffset > maxLocals) {
                maxLocals = varOffset;
            }

            doNew.add(node1); // NEW

            if (requireDup)
                doNew.add(new InsnNode(DUP));

            // restore stack
            for (int j = 0; j < args.length; j++) {
                Type type = args[j];
                varOffset -= type.getSize();

                doNew.add(new VarInsnNode(type.getOpcode(ILOAD), varOffset));

                // clean up store to avoid memory leak?
                if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
                    updateMaxStack = updateMaxStack < 1 ? 1 : updateMaxStack; // an extra slot for ACONST_NULL

                    doNew.add(new InsnNode(ACONST_NULL));

                    doNew.add(new VarInsnNode(type.getOpcode(ISTORE), varOffset));
                }
            }
            instructions.insertBefore(nm, doNew);
            nm = doNew.getLast();
        }

        maxStack += updateMaxStack;
    }

    boolean needsFrameGuard(int opcode, String owner, String name, String desc) {
        /* TODO: need to customize a way enchancer skips classes/methods
        if (owner.startsWith("java/")) {
            System.out.println("SKIP:: " + owner + "." + name + desc);
            return false;
        }
        */

        if (opcode == Opcodes.INVOKEINTERFACE || (opcode == Opcodes.INVOKESPECIAL && !"<init>".equals(name))
                || opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKEVIRTUAL) {
            return true;
        }
        return false;
    }

}