Package org.parboiled.transform

Source Code of org.parboiled.transform.InstructionGroupPreparer$MD5Digester

/*
* Copyright (C) 2009-2010 Mathias Doenitz
*
* 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 org.parboiled.transform;

import com.google.common.base.Preconditions;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.EmptyVisitor;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.parboiled.common.Base64;
import org.parboiled.common.StringUtils;

import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

class InstructionGroupPreparer implements RuleMethodProcessor, Opcodes {

    private static final Object lock = new Object();

    private static final Base64 CUSTOM_BASE64 =
            new Base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789äö_");

    private RuleMethod method;

    public boolean appliesTo(@NotNull ParserClassNode classNode, @NotNull RuleMethod method) {
        return method.containsExplicitActions() || method.containsVars();
    }

    public void process(@NotNull ParserClassNode classNode, @NotNull RuleMethod method) {
        this.method = method;

        // prepare groups for later stages
        for (InstructionGroup group : method.getGroups()) {
            extractInstructions(group);
            extractFields(group);
            name(group, classNode);
        }
    }

    // move all group instructions except for the root from the underlying method into the groups Insnlist
    private void extractInstructions(InstructionGroup group) {
        for (InstructionGraphNode node : group.getNodes()) {
            if (node != group.getRoot()) {
                AbstractInsnNode insn = node.getInstruction();
                method.instructions.remove(insn);
                group.getInstructions().add(insn);
            }
        }
    }

    // create FieldNodes for all xLoad instructions
    private void extractFields(InstructionGroup group) {
        List<FieldNode> fields = group.getFields();
        for (InstructionGraphNode node : group.getNodes()) {
            if (node.isXLoad()) {
                VarInsnNode insn = (VarInsnNode) node.getInstruction();

                // check whether we already have a field for the var with this index
                int index;
                for (index = 0; index < fields.size(); index++) {
                    if (fields.get(index).access == insn.var) break;
                }

                // if we don't, create a new field for the var
                if (index == fields.size()) {
                    // CAUTION, HACK!: for brevity we reuse the access field and the value field of the FieldNode
                    // for keeping track of the original var index as well as the FieldNodes Type (respectively)
                    // so we need to make sure that we correct for this when the field is actually written
                    Type type = node.getResultValue().getType();
                    fields.add(new FieldNode(insn.var, "field$" + index, type.getDescriptor(), null, type));
                }

                // normalize the instruction so instruction groups that are identical except for the variable
                // indexes are still mapped to the same group class (name)
                insn.var = index;
            }
        }
    }

    // set a group name base on the hash across all group instructions
    private void name(InstructionGroup group, ParserClassNode classNode) {
        synchronized (lock) {
            // generate an MD5 hash across the buffer, use only the first 96 bit
            MD5Digester digester = new MD5Digester(classNode.name);
            group.getInstructions().accept(digester);
            byte[] hash = digester.getMD5Hash();
            byte[] hash96 = new byte[12];
            System.arraycopy(hash, 0, hash96, 0, 12);

            // generate a name for the group based on the hash
            String name = group.getRoot().isActionRoot() ? "Action$" : "VarInit$";
            name += CUSTOM_BASE64.encodeToString(hash96, false);
            group.setName(name);
        }
    }

    private static class MD5Digester extends EmptyVisitor {
        private static MessageDigest digest;
        private static ByteBuffer buffer;
        private final List<Label> labels = new ArrayList<Label>();
        private final String parserClassName;

        public MD5Digester(String parserClassName) {
            this.parserClassName = parserClassName;
            if (digest == null) {
                try {
                    digest = MessageDigest.getInstance("MD5");
                } catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException(e);
                }
            }
            if (buffer == null) {
                buffer = ByteBuffer.allocateDirect(4096);
            }
            buffer.clear();
        }

        @Override
        public void visitInsn(int opcode) {
            update(opcode);
        }

        @Override
        public void visitIntInsn(int opcode, int operand) {
            update(opcode);
            update(operand);
        }

        @Override
        public void visitVarInsn(int opcode, int var) {
            update(opcode);
            update(var);
            if (opcode == ALOAD && var == 0) {
                // make sure the names of identical actions differ if they are defined in different parent classes
                update(parserClassName);
            }
        }

        @Override
        public void visitTypeInsn(int opcode, String type) {
            update(opcode);
            update(type);
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
            update(opcode);
            update(owner);
            update(name);
            update(desc);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc) {
            update(opcode);
            update(owner);
            update(name);
            update(desc);
        }

        @Override
        public void visitJumpInsn(int opcode, Label label) {
            update(opcode);
            update(label);
        }

        @Override
        public void visitLabel(Label label) {
            update(label);
        }

        @Override
        public void visitLdcInsn(Object cst) {
            if (cst instanceof String) {
                update((String) cst);
            } else if (cst instanceof Integer) {
                update((Integer) cst);
            } else if (cst instanceof Float) {
                ensureRemaining(4);
                buffer.putFloat((Float) cst);
            } else if (cst instanceof Long) {
                ensureRemaining(8);
                buffer.putLong((Long) cst);
            } else if (cst instanceof Double) {
                ensureRemaining(8);
                buffer.putDouble((Double) cst);
            } else {
                Preconditions.checkState(cst instanceof Type);
                update(((Type) cst).getInternalName());
            }
        }

        @Override
        public void visitIincInsn(int var, int increment) {
            update(var);
            update(increment);
        }

        @Override
        public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
            update(min);
            update(max);
            update(dflt);
            for (Label label : labels) {
                update(label);
            }
        }

        @Override
        public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
            update(dflt);
            for (int i = 0; i < keys.length; i++) {
                update(keys[i]);
                update(labels[i]);
            }
        }

        @Override
        public void visitMultiANewArrayInsn(String desc, int dims) {
            update(desc);
            update(dims);
        }

        @Override
        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
            update(start);
            update(end);
            update(handler);
            update(type);
        }

        private void update(int i) {
            ensureRemaining(4);
            buffer.putInt(i);
        }

        private void update(String str) {
            if (StringUtils.isNotEmpty(str)) {
                int len = str.length();
                ensureRemaining(len * 2);
                for (int i = 0; i < len; i++) {
                    buffer.putChar(str.charAt(i));
                }
            }
        }

        private void update(Label label) {
            int index = labels.indexOf(label);
            if (index == -1) {
                index = labels.size();
                labels.add(label);
            }
            update(index);
        }

        private void ensureRemaining(int bytes) {
            if (buffer.remaining() < bytes) {
                digest();
            }
        }

        private void digest() {
            buffer.flip();
            digest.update(buffer);
            buffer.clear();
        }

        public byte[] getMD5Hash() {
            digest();
            return digest.digest();
        }
    }

}
TOP

Related Classes of org.parboiled.transform.InstructionGroupPreparer$MD5Digester

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.