Package de.loskutov.bco.asm

Source Code of de.loskutov.bco.asm.DecompiledMethod

package de.loskutov.bco.asm;

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

import org.eclipse.core.runtime.IStatus;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SimpleVerifier;
import org.objectweb.asm.tree.analysis.Value;

import de.loskutov.bco.BytecodeOutlinePlugin;
import de.loskutov.bco.preferences.BCOConstants;

/**
* @author Eric Bruneton
*/

public class DecompiledMethod {

    private final List text;

    private final List localVariables;

    /**
     * decompiled line -> source line
     */
    private final Map sourceLines;

    /**
     * source line -> decompiled line
     */
    private final Map decompiledLines;

    /**
     * decompiled line -> insn
     */
    private final Map insns;

    /**
     *  decompiled line -> opcode
     */
    private final Map opcodes;

    /**
     * insn -> decompile line
     */
    private final Map insnLines;

    private int lineCount;

    /**
     * first source line, if any
     */
    private int firstSourceLine;

    /**
     * last source line, if any
     */
    private int lastSourceLine;

    private final MethodNode meth;

    private Frame[] frames;

    private String error;

    private int errorInsn;

    private final String owner;


    public DecompiledMethod(final String owner, final List inputText,
        final Map lineNumbers, final MethodNode meth, final ClassLoader cl, BitSet modes) {
        this.owner = owner;
        this.text = new ArrayList();
        this.localVariables = meth.localVariables;
        this.sourceLines = new HashMap();
        this.decompiledLines = new HashMap();
        this.insns = new HashMap();
        this.opcodes = new HashMap();
        this.insnLines = new HashMap();

        this.meth = meth;
        formatText(inputText, new HashMap(), new StringBuffer(), this.text);
        computeMaps(lineNumbers);

        if (modes.get(BCOConstants.F_SHOW_ANALYZER)
            && (meth.access & Opcodes.ACC_ABSTRACT) == 0) {
            analyzeMethod(cl);
        }
    }

    public boolean isInit() {
        return ("<init>".equals(meth.name) && "()V".equals(meth.desc))
            || "<clinit>".equals(meth.name);
    }

    public boolean hasSourceLinesInfo(){
        return ! sourceLines.isEmpty();
    }

    public boolean hasLocalVariablesInfo(){
        return ! localVariables.isEmpty();
    }

    public String getSignature(){
        return meth.name + meth.desc;
    }

    public boolean containsSource(int sourceLine){
        return sourceLine >= getFirstSourceLine() && sourceLine <= getLastSourceLine();
    }

    /**
     * @param sourceLine
     * @return nearest match above given source line or the given line for perfect match
     * or -1 for no match. The return value is method-relative, and need to be transformed
     * to class absolute
     */
    public int getBestDecompiledLine(final int sourceLine){
        if(!containsSource(sourceLine)){
            return -1;
        }
        Set set = decompiledLines.keySet();
        if(set.size() == 0){
            return -1;
        }
        int bestMatch = -1;
        for (Iterator iter = set.iterator(); iter.hasNext();) {
            int line = ((Integer) iter.next()).intValue();
            int delta = sourceLine - line;
            if(delta < 0){
                continue;
            } else if(delta == 0){
                return line;
            }
            if(bestMatch < 0 || delta < sourceLine - bestMatch){
                bestMatch = line;
            }
        }
        if(bestMatch < 0){
            return -1;
        }
        return ((Integer)decompiledLines.get(new Integer(bestMatch))).intValue();
    }

    /**
     * @param owner
     * @param meth
     * @param cl
     */
    private void analyzeMethod(final ClassLoader cl) {
        Analyzer a = new Analyzer(new SimpleVerifier() {

            protected Class getClass(final Type t) {
                try {
                    if (t.getSort() == Type.ARRAY) {
                        return Class.forName(t.getDescriptor().replace(
                            '/', '.'), true, cl);
                    }
                    return cl.loadClass(t.getClassName());
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e.toString()+" " +cl, e);
                }
            }
        });
        try {
            a.analyze(owner, meth);
        } catch (AnalyzerException e) {
            error = e.getMessage();
            if (error.startsWith("Error at instruction ")) {
                error = error.substring("Error at instruction ".length());
                errorInsn = Integer.parseInt(error.substring(0, error
                    .indexOf(':')));
                error = error.substring(error.indexOf(':') + 2);
            } else {
                BytecodeOutlinePlugin.log(e, IStatus.ERROR);
                error = null;
            }
        }
        frames = a.getFrames();
    }

    private void formatText(final List input, final Map locals, StringBuffer line,
        final List result) {
        for (int i = 0; i < input.size(); ++i) {
            Object o = input.get(i);
            if (o instanceof List) {
                formatText((List) o, locals, line, result);
            } else if (o instanceof Index) {
                result.add(o);
                updateLocals((Index) o, locals);
            } else if (o instanceof Integer) {
                String localVariableName = (String) locals.get(o);
                if (localVariableName == null) {
                    Index index = getNextIndex(input, i);
                    if(index != null){
                        updateLocals(index, locals);
                        localVariableName = (String) locals.get(o);
                    }
                }
                if(localVariableName != null) {
                    line.append(": ").append(localVariableName);
                }
            } else {
                String s = o.toString();
                int p;
                do {
                    p = s.indexOf('\n');
                    if (p == -1) {
                        line.append(s);
                    } else {
                        result.add(line.toString() + s.substring(0, p + 1));
                        s = s.substring(p + 1);
                        line.setLength(0);
                    }
                } while (p != -1);
            }
        }
    }

    /**
     * @param i
     * @param input
     * @return
     */
    private Index getNextIndex(List input, int startOffset) {
        for (int i = startOffset + 1; i < input.size(); i++) {
            Object object = input.get(i);
            if(object instanceof Index){
                return (Index)object;
            }
        }
        return null;
    }

    private void updateLocals(final Index index, final Map locals) {
        for (int i = 0; i < localVariables.size(); ++i) {
            LocalVariableNode lvNode = (LocalVariableNode) localVariables.get(i);
            if (lvNode.start == index.labelNode) {
                locals.put(new Integer(lvNode.index), lvNode.name);
            } else if (lvNode.end == index.labelNode) {
                locals.remove(new Integer(lvNode.index));
            }
        }
    }

    private void computeMaps(final Map lineNumbers) {
        int currentDecompiledLine = 0;
        int firstLine = -1;
        int lastLine = -1;
        for (int i = 0; i < text.size(); ++i) {
            int currentOpcode = -1;
            int currentInsn = -1;
            int currentSourceLine = -1;
            Object o = text.get(i);
            if (o instanceof Index) {
                Index index = (Index) o;
                Integer sourceLine = null;
                if(index.labelNode != null) {
                    sourceLine = (Integer) lineNumbers.get(index.labelNode.getLabel());
                }
                if (sourceLine != null) {
                    currentSourceLine = sourceLine.intValue();
                    if(firstLine == -1 || currentSourceLine < firstLine){
                        firstLine = currentSourceLine;
                    }
                    if(lastLine == -1 || currentSourceLine > lastLine){
                        lastLine = currentSourceLine;
                    }
                }
                currentInsn = index.insn;
                currentOpcode = index.opcode;
            } else {
                ++currentDecompiledLine;
            }
            Integer cdl = new Integer(currentDecompiledLine);
            Integer ci = new Integer(currentInsn);
            Integer co = new Integer(currentOpcode);
            if(currentSourceLine >= 0){
                Integer csl = new Integer(currentSourceLine);
                sourceLines.put(cdl, csl);
                if (decompiledLines.get(csl) == null) {
                    decompiledLines.put(csl, cdl);
                }
            }
            insns.put(cdl, ci);
            opcodes.put(cdl, co);
            if (insnLines.get(ci) == null) {
                insnLines.put(ci, cdl);
            }
        }
        lineCount = currentDecompiledLine;
        firstSourceLine = firstLine;
        lastSourceLine = lastLine;
    }

    public String getText() {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < text.size(); ++i) {
            Object o = text.get(i);
            if (!(o instanceof Index)) {
                buf.append((String) o);
            }
        }
        return buf.toString();
    }

    public String[][] getTextTable() {
        Frame frame = null;
        String error1 = "";
        List lines = new ArrayList();
        String offsStr = null;
        for (int i = 0; i < text.size(); ++i) {
            Object o = text.get(i);
            if (o instanceof Index) {
                Index index = (Index) o;
                int insn = index.insn;

                offsStr = "" + insn;
                if (frames != null && insn < frames.length) {
                    frame = frames[insn];
                    if (this.error != null && insn == this.errorInsn) {
                      error1 = this.error;
                    }
                }
            } else {
                if(offsStr == null){
                    offsStr = "";
                }
                String locals = " ";
                String stack = " ";
                if (frame != null) {
                    StringBuffer buf = new StringBuffer();
                    appendFrame(buf, frame);
                    int p = buf.indexOf(" ");
                    locals = buf.substring(0, p);
                    if("".equals(locals)){
                        locals = " ";
                    }
                    stack = buf.substring(p + 1);
                    if("".equals(stack)){
                        stack = " ";
                    }
                }

                lines.add(new String[]{offsStr, locals, stack, o.toString(), error1});
                frame = null;
                error1 = "";
                offsStr = null;
            }
        }
        return (String[][]) lines.toArray(new String[lines.size()][]);
    }

    public int getLineCount() {
        return lineCount;
    }

    public String getError() {
        return error;
    }

    public int getErrorLine() {
        if (error == null) {
            return -1;
        }
        Integer i = (Integer) insnLines.get(new Integer(errorInsn));
        return i == null
            ? -1
            : i.intValue();
    }

    private void appendFrame(final StringBuffer buf, final Frame f) {
        try {
            for (int i = 0; i < f.getLocals(); ++i) {
                appendValue(buf, f.getLocal(i));
            }
            buf.append(' ');
            for (int i = 0; i < f.getStackSize(); ++i) {
                appendValue(buf, f.getStack(i));
            }
        } catch (IndexOutOfBoundsException e) {
            // TODO should we keep this?
            BytecodeOutlinePlugin.log(e, IStatus.WARNING);
        }
    }

    private void appendValue(final StringBuffer buf, final Value v) {
        if (((BasicValue) v).isReference()) {
            buf.append("R");
        } else {
            buf.append(v.toString());
        }
    }

    public int getFirstSourceLine(){
        return firstSourceLine;
    }

    public int getLastSourceLine(){
        return lastSourceLine;
    }

    public int getSourceLine(final int decompiledLine) {
        Integer i = (Integer) sourceLines.get(new Integer(decompiledLine));
        return i == null
            ? -1
            : i.intValue();
    }

    /**
     *
     * @param decompiledLine
     * @return array with two elements, first is the local variables table,
     * second is the operands stack content. "null" value could be returned too.
     */
    public String[] getFrame(final int decompiledLine, final boolean useQualifiedNames) {
        Integer insn = getBytecodeOffset(decompiledLine);
        if (error != null && insn != null && insn.intValue() == errorInsn) {
            return new String [] {error,error};
        }
        if (frames != null && insn != null) {
            Frame f = frames[insn.intValue()];
            if (f == null) {
                return null;
            }

            try {
                StringBuffer localsBuf = new StringBuffer();

                for (int i = 0; i < f.getLocals(); ++i) {
                    String s = f.getLocal(i).toString();
                    appendTypeName(i, useQualifiedNames, localsBuf, s);

                    for (Iterator it = localVariables.iterator(); it.hasNext();) {
                        LocalVariableNode lvnode = (LocalVariableNode) it.next();
                        int n = lvnode.index;
                        if( n==i) {
                          localsBuf.append( " : ").append( lvnode.name);
                        }
                    }

                    localsBuf.append('\n');
                }
                StringBuffer stackBuf = new StringBuffer();
                for (int i = 0; i < f.getStackSize(); ++i) {
                    String s = f.getStack(i).toString();
                    appendTypeName(i, useQualifiedNames, stackBuf, s);
                    stackBuf.append('\n');
                }
                return new String[] {localsBuf.toString(), stackBuf.toString()};
            } catch (IndexOutOfBoundsException e) {
                // TODO should we keep this?
                BytecodeOutlinePlugin.log(e, IStatus.WARNING);
            }
        }
        return null;
    }

    /**
     * @param decompiledLine
     * @return
     */
    public Integer getBytecodeOffset(final int decompiledLine) {
        Integer insn = (Integer) insns.get(new Integer(decompiledLine));
        return insn;
    }

    /**
     * @param decompiledLine
     * @return
     */
    public Integer getBytecodeInsn(final int decompiledLine) {
        Integer insn = (Integer) opcodes.get(new Integer(decompiledLine));
        return insn;
    }

    public String[][][] getFrameTables(final int decompiledLine, boolean useQualifiedNames) {
        Integer insn = getBytecodeOffset(decompiledLine);
        if(insn == null){
            return null;
        }
        return getFrameTablesForInsn(insn.intValue(), useQualifiedNames);
    }

    public String[][][] getFrameTablesForInsn(final int insn, boolean useQualifiedNames) {
        if (error != null && insn == errorInsn) {
            return null;
        }
        if (frames != null && insn >= 0 && insn < frames.length) {
            Frame f = frames[insn];
            if (f == null) {
                return null;
            }

            try {
                ArrayList locals = new ArrayList();
                for (int i = 0; i < f.getLocals(); ++i) {
                    String varName = "";
                    for (Iterator it = localVariables.iterator(); it.hasNext();) {
                        LocalVariableNode lvnode = (LocalVariableNode) it.next();
                        int n = lvnode.index;
                        if( n==i) {
                            varName = lvnode.name;
                            // TODO take into account variable scope!
                            break;
                        }
                    }

                    locals.add( new String[] {
                        ""+i,
                        getTypeName( useQualifiedNames, f.getLocal(i).toString()),
                        varName});
                }

                ArrayList stack = new ArrayList();
                for (int i = 0; i < f.getStackSize(); ++i) {
                    stack.add( new String[] {
                        ""+i,
                        getTypeName( useQualifiedNames, f.getStack(i).toString())});
                }
                return new String[][][] {
                    (String[][]) locals.toArray( new String[ 3][]),
                    (String[][]) stack.toArray( new String[ 2][])};
            } catch (IndexOutOfBoundsException e) {
                // TODO should we keep this?
                BytecodeOutlinePlugin.log(e, IStatus.ERROR);
            }
        }
        return null;
    }


    /**
     * Appends full type name or only simply name, depends on boolean flag.
     *
     * @param useQualifiedNames if false, then e.g. "Object" will be appended to
     * buffer instead of "Ljava/lang/Object;" etc
     * @param buf buffer to append
     * @param s string with bytecode type name, like "Ljava/lang/Object;"
     */
    private void appendTypeName(int n, final boolean useQualifiedNames, StringBuffer buf, String s) {
        buf.append(n).append( " ");
        if(!useQualifiedNames) {
            int idx = s.lastIndexOf('/');
            if(idx > 0){
                // from "Ljava/lang/Object;" to "Object"
                buf.append(s.substring(idx + 1, s.length() - 1));
                return;
            }
        }
        if("Lnull;".equals(s)){
            buf.append("null");
        } else {
            buf.append(s);
        }
    }

    private String getTypeName(final boolean useQualifiedNames, String s) {
      if (!useQualifiedNames) {
          // get leading array symbols
          String arraySymbols = "";
          while (s.startsWith("[")){
              arraySymbols += "[";
              s = s.substring(1);
          }

          int idx = s.lastIndexOf('/');
          if (idx > 0) {
              // from "Ljava/lang/Object;" to "Object"
              return arraySymbols  + s.substring(idx + 1, s.length() - 1);
          }
          // this is the case on LVT view - ignore it
          if("." == s){
              return arraySymbols  + s;
          }
          // resolve primitive types
          return arraySymbols +
              CommentedClassVisitor.getSimpleName(Type.getType(s));
      }
      return "Lnull;".equals(s) ? "null" : s;
    }

    public int getDecompiledLine(final int sourceLine) {
        Integer i = (Integer) decompiledLines.get(new Integer(sourceLine));
        return i == null
            ? -1
            : i.intValue();
    }

    /**
     * Returns <code>true</code> if this <code>DecompiledMethod</code> is the same as the o argument.
     *
     * @return <code>true</code> if this <code>DecompiledMethod</code> is the same as the o argument.
     */
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof DecompiledMethod)) {
            return false;
        }
        DecompiledMethod another = (DecompiledMethod) o;
        return getSignature().equals(another.getSignature())
            && (owner != null? owner.equals(another.owner) : true);
    }

    public int hashCode() {
        return getSignature().hashCode() + (owner != null? owner.hashCode() : 0);
    }
}
TOP

Related Classes of de.loskutov.bco.asm.DecompiledMethod

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.