All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.janino.CodeContext Maven / Gradle / Ivy


/*
 * Janino - An embedded Java[TM] compiler
 *
 * Copyright (c) 2001-2010 Arno Unkrig. All rights reserved.
 * Copyright (c) 2015-2016 TIBCO Software Inc. All rights reserved. // CHECKSTYLE:OFF CHECKSTYLE:ON
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *       following disclaimer in the documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *       products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.codehaus.janino;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.codehaus.commons.compiler.InternalCompilerException;
import org.codehaus.commons.nullanalysis.Nullable;
import org.codehaus.janino.util.ClassFile;
import org.codehaus.janino.util.ClassFile.AttributeInfo;
import org.codehaus.janino.util.ClassFile.ConstantClassInfo;
import org.codehaus.janino.util.ClassFile.LineNumberTableAttribute.Entry;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.AppendFrame;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.ChopFrame;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.FullFrame;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.ObjectVariableInfo;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.SameFrame;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.SameFrameExtended;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.SameLocals1StackItemFrame;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.SameLocals1StackItemFrameExtended;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.StackMapFrame;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.UninitializedVariableInfo;
import org.codehaus.janino.util.ClassFile.StackMapTableAttribute.VerificationTypeInfo;

/**
 * The context of the compilation of a function (constructor or method). Manages generation of byte code, the exception
 * table, generation of line number tables, allocation of local variables, determining of stack size and local variable
 * table size and flow analysis.
 */
public
class CodeContext {

    private static final int     INITIAL_SIZE   = 128;

    private final ClassFile classFile;

    private int                             maxStack;
    private short                           maxLocals;
    private byte[]                          code;
    private final Offset                    beginning;
    private final Inserter                  end;
    private Inserter                        currentInserter;
    private final List exceptionTableEntries;

    /**
     * All the local variables that are allocated in any block in this {@link CodeContext}.
     */
    private final List allLocalVars = new ArrayList();

    /**
     * Each List of Java.LocalVariableSlot is the local variables allocated for a block. They are pushed and popped
     * onto the list together to make allocation of the next local variable slot easy.
     */
    @Nullable private LocalScope currentLocalScope;

    static
    class LocalScope {

        @Nullable final LocalScope         parent;
        final short                        startingLocalVariableSlot;
        final List localVars = new ArrayList();
        final StackMap                     startingStackMap;

        LocalScope(@Nullable LocalScope parent, short startingLocalSlot, StackMap startingStackMap) {
            this.parent                    = parent;
            this.startingLocalVariableSlot = startingLocalSlot;
            this.startingStackMap          = startingStackMap;
        }
    }

    private short                   nextLocalVariableSlot;
    private final List relocatables = new ArrayList();

    /**
     * Creates an empty "Code" attribute.
     */
    public
    CodeContext(ClassFile classFile) {
        this.classFile = classFile;

        this.maxStack               = 0;
        this.maxLocals              = 0;
        this.code                   = new byte[CodeContext.INITIAL_SIZE];
        this.exceptionTableEntries  = new ArrayList();

        this.beginning              = new Offset();
        this.beginning.offset       = 0;

        this.currentInserter        = new Inserter();
        this.currentInserter.offset = 0;
        this.currentInserter.setStackMap(new StackMap(new VerificationTypeInfo[0], new VerificationTypeInfo[0]));

        this.beginning.next         = this.currentInserter;
        this.currentInserter.prev   = this.beginning;

        this.end                    = this.currentInserter;
    }

    /**
     * The {@link ClassFile} this context is related to.
     */
    public ClassFile
    getClassFile() { return this.classFile; }

    /**
     * Allocates space for a local variable of the given size (1 or 2) on the local variable array.
     * 

* As a side effect, the "max_locals" field of the "Code" attribute is updated. *

*

* The only way to deallocate local variables is to {@link #saveLocalVariables()} and later {@link * #restoreLocalVariables()}. *

* * @param size The number of slots to allocate (1 or 2) * @return The slot index of the allocated variable */ public short allocateLocalVariable(short size) { return this.allocateLocalVariable(size, null, null).getSlotIndex(); } /** * Allocates space for a local variable of the given size (1 or 2) on the local variable array. As a side effect, * the "max_locals" field of the "Code" attribute is updated. *

* The only way to deallocate local variables is to {@link #saveLocalVariables()} and later {@link * #restoreLocalVariables()}. *

* * @param size Number of slots to use (1 or 2) * @param name The variable name; if {@code null}, then the variable won't be written to the LocalVariableTable * @param type The variable type; if the name is not null, then the type is needed to write to the * LocalVariableTable */ public Java.LocalVariableSlot allocateLocalVariable(short size, @Nullable String name, @Nullable IClass type) { LocalScope currentScope = this.currentLocalScope; assert currentScope != null : "saveLocalVariables must be called first"; final List currentVars = currentScope.localVars; Java.LocalVariableSlot slot = new Java.LocalVariableSlot(name, this.nextLocalVariableSlot, type); if (name != null) { slot.setStart(this.newOffset()); } // // This check would be wrong, because one (Long|Double)_variable_info maps TWO local variable slots. // if (this.nextLocalVariableSlot != this.currentInserter.stackMap.locals().length) { // throw new InternalCompilerException(...); // } this.nextLocalVariableSlot += size; currentVars.add(slot); this.allLocalVars.add(slot); if (this.nextLocalVariableSlot > this.maxLocals) { this.maxLocals = this.nextLocalVariableSlot; } return slot; } /** * Remembers the current size of the local variables array. */ public List saveLocalVariables() { // Push empty list on the local scope stack to hold a new block's local vars. return (this.currentLocalScope = new LocalScope( this.currentLocalScope, this.nextLocalVariableSlot, this.currentInserter.getStackMap() )).localVars; } /** * Restores the previous size of the local variables array. This MUST to be called for every call to * saveLocalVariables as it closes the variable extent for all the active local variables in the current block. */ public void restoreLocalVariables() { // Pop the list containing the current block's local vars. LocalScope scopeToPop = this.currentLocalScope; assert scopeToPop != null; for (Java.LocalVariableSlot slot : scopeToPop.localVars) { if (slot.getName() != null) slot.setEnd(this.newOffset()); this.allLocalVars.remove(slot); } this.currentLocalScope = scopeToPop.parent; // Truncate the stack map. if (this.currentLocalScope != null) { StackMap sm = this.currentInserter.getStackMap(); if (sm != null) { int numLocalsInStackMap = 0; for (VerificationTypeInfo slot : sm.locals()) { if (slot != StackMapTableAttribute.TOP_VARIABLE_INFO) { numLocalsInStackMap++; } } while (numLocalsInStackMap-- > this.allLocalVars.size()) { sm = sm.popLocal(); } this.currentInserter.setStackMap(sm); } } // reuse local variable slots of the popped scope this.nextLocalVariableSlot = scopeToPop.startingLocalVariableSlot; } /** * @param lineNumberTableAttributeNameIndex 0 == don't generate a "LineNumberTable" attribute */ protected void storeCodeAttributeBody( DataOutputStream dos, short lineNumberTableAttributeNameIndex, short localVariableTableAttributeNameIndex, short stackMapTableAttributeNameIndex ) throws IOException { dos.writeShort(this.maxStack); // max_stack dos.writeShort(this.maxLocals); // max_locals dos.writeInt(this.end.offset); // code_length dos.write(this.code, 0, this.end.offset); // code dos.writeShort(this.exceptionTableEntries.size()); // exception_table_length for (ExceptionTableEntry exceptionTableEntry : this.exceptionTableEntries) { // exception_table dos.writeShort(exceptionTableEntry.startPc.offset); dos.writeShort(exceptionTableEntry.endPc.offset); dos.writeShort(exceptionTableEntry.handlerPc.offset); dos.writeShort(exceptionTableEntry.catchType); } List attributes = new ArrayList(); // Add "LineNumberTable" attribute. if (lineNumberTableAttributeNameIndex != 0) { List lnt = new ArrayList(); for (Offset o = this.beginning; o != null; o = o.next) { if (o instanceof LineNumberOffset) { int offset = o.offset; if (offset > 0xffff) { throw new InternalCompilerException("LineNumberTable entry offset out of range"); } short lineNumber = ((LineNumberOffset) o).lineNumber; lnt.add(new ClassFile.LineNumberTableAttribute.Entry((short) offset, lineNumber)); } } ClassFile.LineNumberTableAttribute.Entry[] lnte = (ClassFile.LineNumberTableAttribute.Entry[]) lnt.toArray( new ClassFile.LineNumberTableAttribute.Entry[lnt.size()] ); attributes.add(new ClassFile.LineNumberTableAttribute( lineNumberTableAttributeNameIndex, // attributeNameIndex lnte // lineNumberTableEntries )); } // Add "LocalVariableTable" attribute. if (localVariableTableAttributeNameIndex != 0) { ClassFile.AttributeInfo ai = this.storeLocalVariableTable(dos, localVariableTableAttributeNameIndex); if (ai != null) attributes.add(ai); } // Add the "StackMapTable" attribute. { // Skip the "zeroth" frame. Offset frame = this.beginning; Offset previousFrame = null; while (frame.offset == 0) { previousFrame = frame; frame = frame.next; assert frame != null; } assert previousFrame != null; List smfs = new ArrayList(); for (; frame != null && frame.offset != this.end.offset; frame = frame.next) { // "Padder" is used to insert the padding bytes of the LOOKUPSWITCH instruction; skip it, because // it sits INSIDE the instruction and would cause a "StackMapTable error: bad offset". if (frame instanceof Java.Padder) continue; // "FourByteOffset" is used to write the offsets of the LOOKUPSWITCH instruction. Skip it, because // it sits INSIDE the instruction and would cause a "StackMapTable error: bad offset". if (frame instanceof FourByteOffset) continue; { Offset next = frame.next; if (next != null && frame.offset == next.offset) continue; } final int offsetDelta = previousFrame.offset == 0 ? frame.offset : frame.offset - previousFrame.offset - 1; final VerificationTypeInfo[] frameOperands = frame.getStackMap().operands(); final int frameOperandsLength = frameOperands.length; final VerificationTypeInfo[] frameLocals = frame.getStackMap().locals(); final int frameLocalsLength = frameLocals.length; final VerificationTypeInfo[] previousFrameLocals = previousFrame.getStackMap().locals(); final int previousFrameLocalsLength = previousFrameLocals.length; int k; // Encode the stack map entry delta as "frames", see JVMS11 4.7.4 if ( frameOperandsLength == 0 && Arrays.equals(frameLocals, previousFrameLocals) ) { if (offsetDelta <= 63) { smfs.add(new SameFrame(offsetDelta)); // same_frame } else { smfs.add(new SameFrameExtended(offsetDelta)); // same_frame_extended } } else if ( frameOperandsLength == 1 && Arrays.equals(frameLocals, previousFrameLocals) ) { if (offsetDelta <= 63) { smfs.add(new SameLocals1StackItemFrame( // same_locals_1_stack_item_frame offsetDelta, // offset_delta frameOperands[0] // stack )); } else { smfs.add(new SameLocals1StackItemFrameExtended( // same_locals_1_stack_item_frame_extended offsetDelta, // offset_delta frameOperands[0] // stack )); } } else if ( frameOperandsLength == 0 && (k = previousFrameLocalsLength - frameLocalsLength) >= 1 && k <= 3 && Arrays.equals(frameLocals, Arrays.copyOf(previousFrameLocals, frameLocalsLength)) ) { smfs.add(new ChopFrame(offsetDelta, k)); // chop_frame } else if ( frameOperandsLength == 0 && (k = frameLocalsLength - previousFrameLocalsLength) >= 1 && k <= 3 && Arrays.equals(previousFrameLocals, Arrays.copyOf(frameLocals, previousFrameLocalsLength)) ) { smfs.add(new AppendFrame( // append_frame offsetDelta, // offset_delta (VerificationTypeInfo[]) Arrays.copyOfRange( // locals frameLocals, previousFrameLocalsLength, frameLocalsLength ) )); } else { smfs.add(new FullFrame( // full_frame offsetDelta, // offset_delta frameLocals, // locals frameOperands // stack )); } previousFrame = frame; } attributes.add( new StackMapTableAttribute( stackMapTableAttributeNameIndex, (StackMapFrame[]) smfs.toArray(new StackMapFrame[smfs.size()]) ) ); } dos.writeShort(attributes.size()); // attributes_count for (ClassFile.AttributeInfo attribute : attributes) { // attributes; attribute.store(dos); } } /** * @return A {@link org.codehaus.janino.util.ClassFile.LocalVariableTableAttribute} for this {@link CodeContext} */ @Nullable protected ClassFile.AttributeInfo storeLocalVariableTable(DataOutputStream dos, short localVariableTableAttributeNameIndex) { ClassFile cf = this.getClassFile(); final List entryList = new ArrayList(); for (Java.LocalVariableSlot slot : this.getAllLocalVars()) { String localVariableName = slot.getName(); if (localVariableName != null) { String typeName = slot.getType().getDescriptor(); final short classSlot = cf.addConstantUtf8Info(typeName); final short varNameSlot = cf.addConstantUtf8Info(localVariableName); Offset start = slot.getStart(); Offset end2 = slot.getEnd(); assert start != null; assert end2 != null; ClassFile.LocalVariableTableAttribute.Entry entry = new ClassFile.LocalVariableTableAttribute.Entry( (short) start.offset, (short) (end2.offset - start.offset), varNameSlot, classSlot, slot.getSlotIndex() ); entryList.add(entry); } } if (entryList.size() > 0) { Object entries = entryList.toArray(new ClassFile.LocalVariableTableAttribute.Entry[entryList.size()]); return new ClassFile.LocalVariableTableAttribute( localVariableTableAttributeNameIndex, (ClassFile.LocalVariableTableAttribute.Entry[]) entries ); } return null; } /** * Fixes up all of the offsets and relocate() all relocatables. */ public void fixUpAndRelocate() { this.maybeGrow(); this.fixUp(); this.relocate(); } /** * Grow the code if relocatables are required to. */ private void maybeGrow() { for (Relocatable relocatable : this.relocatables) { relocatable.grow(); } } /** * Fixes up all offsets. */ private void fixUp() { for (Offset o = this.beginning; o != this.end; o = o.next) { assert o != null; if (o instanceof FixUp) ((FixUp) o).fixUp(); } } /** * Relocates all relocatables. */ private void relocate() { for (Relocatable relocatable : this.relocatables) { relocatable.relocate(); } } /** * Inserts a sequence of bytes at the current insertion position. Creates {@link LineNumberOffset}s as necessary. */ public void write(byte[] b) { if (b.length == 0) return; // CAVEAT: "makeSpace()" may change "this.code", so make sure to call "makeSpace()" _before_ using "this.code"! int o = this.makeSpace(b.length); System.arraycopy(b, 0, this.code, o, b.length); } /** * Inserts a byte at the current insertion position. Creates {@link LineNumberOffset}s as necessary. *

* This method is an optimization to avoid allocating small byte[] and ease GC load. *

*/ public void write(byte b1) { // CAVEAT: "makeSpace()" may change "this.code", so make sure to call "makeSpace()" _before_ using "this.code"! int o = this.makeSpace(1); this.code[o] = b1; } /** * Inserts bytes at the current insertion position. Creates {@link LineNumberOffset}s as necessary. *

* This method is an optimization to avoid allocating small byte[] and ease GC load. *

*/ public void write(byte b1, byte b2) { int o = this.makeSpace(2); this.code[o++] = b1; this.code[o] = b2; } /** * Inserts bytes at the current insertion position. Creates {@link LineNumberOffset}s as necessary. *

* This method is an optimization to avoid allocating small byte[] and ease GC load. *

*/ public void write(byte b1, byte b2, byte b3) { int o = this.makeSpace(3); this.code[o++] = b1; this.code[o++] = b2; this.code[o] = b3; } /** * Inserts bytes at the current insertion position. Creates {@link LineNumberOffset}s as necessary. *

* This method is an optimization to avoid allocating small byte[] and ease GC load. *

*/ public void write(byte b1, byte b2, byte b3, byte b4) { int o = this.makeSpace(4); this.code[o++] = b1; this.code[o++] = b2; this.code[o++] = b3; this.code[o] = b4; } public void addLineNumberOffset(int lineNumber) { if (lineNumber == -1) return; if (lineNumber > 0xffff) lineNumber = 0xffff; // Find out whether the line number is different from the line number of the preceding insertion, // and, if so, insert a LineNumberOffset object, which will later lead to a LineNumberTable entry. for (Offset o = this.currentInserter.prev; o != this.beginning; o = o.prev) { assert o != null; if (o instanceof LineNumberOffset) { if ((((LineNumberOffset) o).lineNumber & 0xffff) == lineNumber) return; break; } } // Insert a LineNumberOffset _before_ the current inserter. LineNumberOffset lno = new LineNumberOffset(this.currentInserter.offset, this.currentInserter.getStackMap(), (short) lineNumber); Offset cip = this.currentInserter.prev; assert cip != null; lno.prev = cip; lno.next = this.currentInserter; cip.next = lno; this.currentInserter.prev = lno; } /** * Inserts size NUL bytes at the current inserter's offset, advances the current inserter's offset by * size, creates {@link LineNumberOffset}s as necessary, and returns the current inserter's original * offset (the offset of the first NUL byte that was inserted). *

* Because the class file format does not support line numbers greater than 65535, these are treated as 65535. *

* * @param size The number of NUL bytes to inject * @return The offset of the first inserted byte */ public int makeSpace(final int size) { final int cio = this.currentInserter.offset; if (size == 0) return cio; if (this.end.offset + size <= this.code.length) { // Optimization to avoid a trivial method call in the common case if (cio != this.end.offset) { System.arraycopy(this.code, cio, this.code, cio + size, this.end.offset - cio); } } else { byte[] oldCode = this.code; //double size to avoid horrible performance, but don't grow over our limit int newSize = Math.max(Math.min(oldCode.length * 2, 0xffff), oldCode.length + size); if (newSize > 0xffff) throw new InternalCompilerException("Code grows beyond 64 KB"); this.code = new byte[newSize]; System.arraycopy(oldCode, 0, this.code, 0, cio); System.arraycopy(oldCode, cio, this.code, cio + size, this.end.offset - cio); } Arrays.fill(this.code, cio, cio + size, (byte) 0); for (Offset o = this.currentInserter; o != null; o = o.next) o.offset += size; return cio; } /** */ public void writeShort(int v) { this.write((byte) (v >> 8), (byte) v); } /** * Generates a "branch" instruction. * * @param opcode One of {@link Opcode#GOTO}, {@link Opcode#JSR} and Opcode.IF* * @param dst Where to branch */ public void writeBranch(int opcode, final Offset dst) { if (dst.offset == -1) { if (dst.stackMap == null) { dst.stackMap = this.currentInserter.getStackMap(); } } this.relocatables.add(new Branch(opcode, dst)); this.write((byte) opcode, (byte) -1, (byte) -1); } private class Branch extends Relocatable { Branch(int opcode, Offset destination) { this.opcode = opcode; this.source = CodeContext.this.newInserter(); this.destination = destination; if (opcode == Opcode.JSR_W || opcode == Opcode.GOTO_W) { // No need to expand wide opcodes. this.expanded = true; } else { this.expanded = false; } } @Override public void grow() { if (this.destination.offset == Offset.UNSET) { throw new InternalCompilerException("Cannot relocate branch to unset destination offset"); } int offset = this.destination.offset - this.source.offset; @SuppressWarnings("deprecation") final int opcodeJsr = Opcode.JSR; if (!this.expanded && (offset > Short.MAX_VALUE || offset < Short.MIN_VALUE)) { //we want to insert the data without skewing our source position, //so we will cache it and then restore it later. final int pos = this.source.offset; CodeContext.this.pushInserter(this.source); { // Promotion to a wide instruction only requires 2 extra bytes. Everything else requires a new // GOTO_W instruction after a negated if (5 extra bytes). CodeContext.this.makeSpace(this.opcode == Opcode.GOTO || this.opcode == opcodeJsr ? 2 : 5); } CodeContext.this.popInserter(); this.source.offset = pos; this.expanded = true; } } @Override public void relocate() { if (this.destination.offset == Offset.UNSET) { throw new InternalCompilerException("Cannot relocate branch to unset destination offset"); } int offset = this.destination.offset - this.source.offset; @SuppressWarnings("deprecation") final int opcodeJsr = Opcode.JSR; final byte[] ba; if (!this.expanded) { //we fit in a 16-bit jump ba = new byte[] { (byte) this.opcode, (byte) (offset >> 8), (byte) offset }; } else { if (this.opcode == Opcode.GOTO || this.opcode == opcodeJsr) { ba = new byte[] { (byte) (this.opcode + 33), // GOTO => GOTO_W; JSR => JSR_W (byte) (offset >> 24), (byte) (offset >> 16), (byte) (offset >> 8), (byte) offset }; } else { //exclude the if-statement from jump target //if jumping backwards this will increase the jump to go over it //if jumping forwards this will decrease the jump by it offset -= 3; // [if cond offset] //expands to // [if !cond skip_goto] // [GOTO_W offset] ba = new byte[] { (byte) CodeContext.invertBranchOpcode(this.opcode), 0, 8, // Jump from this instruction past the GOTO_W (byte) Opcode.GOTO_W, (byte) (offset >> 24), (byte) (offset >> 16), (byte) (offset >> 8), (byte) offset }; } } System.arraycopy(ba, 0, CodeContext.this.code, this.source.offset, ba.length); } private boolean expanded; //marks whether this has been expanded to account for a wide branch private final int opcode; private final Inserter source; private final Offset destination; } /** * E.g. {@link Opcode#IFLT} ("less than") inverts to {@link Opcode#IFGE} ("greater than or equal to"). */ private static int invertBranchOpcode(int branchOpcode) { final Integer result = (Integer) CodeContext.BRANCH_OPCODE_INVERSION.get(Integer.valueOf(branchOpcode)); assert result != null : branchOpcode; return result; } private static final Map BRANCH_OPCODE_INVERSION = CodeContext.createBranchOpcodeInversion(); private static Map createBranchOpcodeInversion() { Map m = new HashMap(); m.put(Opcode.IF_ACMPEQ, Opcode.IF_ACMPNE); m.put(Opcode.IF_ACMPNE, Opcode.IF_ACMPEQ); m.put(Opcode.IF_ICMPEQ, Opcode.IF_ICMPNE); m.put(Opcode.IF_ICMPNE, Opcode.IF_ICMPEQ); m.put(Opcode.IF_ICMPGE, Opcode.IF_ICMPLT); m.put(Opcode.IF_ICMPLT, Opcode.IF_ICMPGE); m.put(Opcode.IF_ICMPGT, Opcode.IF_ICMPLE); m.put(Opcode.IF_ICMPLE, Opcode.IF_ICMPGT); m.put(Opcode.IFEQ, Opcode.IFNE); m.put(Opcode.IFNE, Opcode.IFEQ); m.put(Opcode.IFGE, Opcode.IFLT); m.put(Opcode.IFLT, Opcode.IFGE); m.put(Opcode.IFGT, Opcode.IFLE); m.put(Opcode.IFLE, Opcode.IFGT); m.put(Opcode.IFNULL, Opcode.IFNONNULL); m.put(Opcode.IFNONNULL, Opcode.IFNULL); return Collections.unmodifiableMap(m); } /** * Writes a four-byte offset (as it is used in TABLESWITCH and LOOKUPSWITCH) into this code context. */ public void writeOffset(Offset src, final Offset dst) { Offset o = new FourByteOffset(); o.set(); this.relocatables.add(new OffsetBranch(o, src, dst)); this.makeSpace(4); } private final class FourByteOffset extends Offset {} private class OffsetBranch extends Relocatable { OffsetBranch(Offset where, Offset source, Offset destination) { this.where = where; this.source = source; this.destination = destination; } @Override public void grow() {} @Override public void relocate() { if (this.source.offset == Offset.UNSET || this.destination.offset == Offset.UNSET) { throw new InternalCompilerException("Cannot relocate offset branch to unset destination offset"); } int offset = this.destination.offset - this.source.offset; byte[] ba = new byte[] { (byte) (offset >> 24), (byte) (offset >> 16), (byte) (offset >> 8), (byte) offset }; System.arraycopy(ba, 0, CodeContext.this.code, this.where.offset, 4); } private final Offset where, source, destination; } /** * Creates and inserts an {@link CodeContext.Offset} at the current inserter's current position. */ public Offset newOffset() { Offset o = new Offset(); o.set(); return o; } /** * Allocates an {@link Inserter}, set it to the current offset, and inserts it before the current offset. *

* In clear text, this means that you can continue writing to the "Code" attribute, then {@link * #pushInserter(CodeContext.Inserter)} the {@link Inserter}, then write again (which inserts bytes into the * "Code" attribute at the previously remembered position), and then {@link #popInserter()}. *

*/ public Inserter newInserter() { Inserter i = new Inserter(); i.set(); return i; } /** * @return The current inserter */ public Inserter currentInserter() { return this.currentInserter; } /** * Remembers the current {@link Inserter}, then replaces it with the new one. */ public void pushInserter(Inserter ins) { // The current inserter MUST have a stack map; verify. ins.getStackMap(); if (ins.nextInserter != null) { throw new InternalCompilerException("An Inserter can only be pushed once at a time"); } ins.nextInserter = this.currentInserter; this.currentInserter = ins; } /** * Replaces the current {@link Inserter} with the remembered one (see {@link #pushInserter(CodeContext.Inserter)}). */ public void popInserter() { Inserter ni = this.currentInserter.nextInserter; if (ni == null) throw new InternalCompilerException("Code inserter stack underflow"); ni.getStackMap(); this.currentInserter.nextInserter = null; // Mark it as "unpushed". this.currentInserter = ni; } /** * A class that represents an offset within a "Code" attribute. *

* The concept of an "offset" is that if one writes into the middle of a "Code" attribute, all offsets behind the * insertion point are automatically shifted. *

*/ public class Offset { /** * The offset in the code attribute that this object represents. */ int offset = Offset.UNSET; /** * Links to preceding and succeeding offsets. Both are {@code null} before {@link #set()} is called, * and both are non-{@code null} after {@link #set()} has been called. This implies that {@link * #set()} must be invoked at most once. */ @Nullable Offset prev, next; /** * Special value for {@link #offset} which indicates that this {@link Offset} has not yet been {@link #set()} */ static final int UNSET = -1; @Nullable private StackMap stackMap; // Is null until "set()". /** * Sets this "Offset" to the offset of the current inserter; inserts this "Offset" before the current inserter. */ public void set() { this.setOffset(); this.setStackMap(); Inserter ci = CodeContext.this.currentInserter; Offset cip = ci.prev; assert cip != null; this.prev = cip; this.next = ci; cip.next = this; ci.prev = this; } /** * Merges the stack maps of the current inserter and THIS offset, and assigns the result to the current * inserter and THIS offset. */ void setStackMap() { Inserter ci = CodeContext.this.currentInserter; ((Offset) ci).stackMap = this.stackMap = CodeContext.mergeStackMaps(((Offset) ci).stackMap, this.stackMap); } public void setOffset() { Inserter ci = CodeContext.this.currentInserter; if (this.offset != Offset.UNSET) throw new InternalCompilerException("Offset already set"); this.offset = ci.offset; } public StackMap getStackMap() { return this.stackMap; } public void setStackMap(StackMap stackMap) { this.stackMap = stackMap; } /** * @return The {@link CodeContext} that this {@link Offset} belongs to */ public final CodeContext getCodeContext() { return CodeContext.this; } @Override public String toString() { return CodeContext.this.classFile.getThisClassName() + ": " + this.offset; } } @Nullable private static final StackMap mergeStackMaps(@Nullable StackMap sm1, @Nullable StackMap sm2) { if (sm1 == null) return sm2; if (sm2 == null) return sm1; if (sm1 == sm2) return sm1; if (sm1.equals(sm2)) return sm1; if (!Arrays.equals(sm1.operands(), sm2.operands())) { throw new InternalCompilerException("Inconsistent operand stack: " + sm1 + " vs. " + sm2); } VerificationTypeInfo[] locals1 = sm1.locals(); VerificationTypeInfo[] locals2 = sm2.locals(); VerificationTypeInfo[] tmp = new VerificationTypeInfo[Math.min(locals1.length, locals2.length)]; for (int i = 0; i < tmp.length; i++) { VerificationTypeInfo local1 = locals1[i]; VerificationTypeInfo local2 = locals2[i]; VerificationTypeInfo tmpLocal; if (local1.equals(local2)) { tmpLocal = local1; } else if (local1 == StackMapTableAttribute.TOP_VARIABLE_INFO || local2 == StackMapTableAttribute.TOP_VARIABLE_INFO) { tmpLocal = StackMapTableAttribute.TOP_VARIABLE_INFO; } else { // This check would be wrong, because the different flow paths may have used the slot // for different locals. // throw new InternalCompilerException( // "Inconsistent local variable #" + i + " verification type: " + sm1 + " vs. " + sm2 // ); tmpLocal = StackMapTableAttribute.TOP_VARIABLE_INFO; } tmp[i] = tmpLocal; } return new StackMap(tmp, sm1.operands()); } /** * Adds another entry to the "exception_table" of this code attribute (see JVMS 4.7.3). * * @param catchTypeFd {@code null} means {@code finally} clause */ public void addExceptionTableEntry(Offset startPc, Offset endPc, Offset handlerPc, @Nullable String catchTypeFd) { this.exceptionTableEntries.add(new ExceptionTableEntry( startPc, endPc, handlerPc, catchTypeFd == null ? (short) 0 : this.classFile.addConstantClassInfo(catchTypeFd) )); } /** * Representation of an entry in the "exception_table" of a "Code" attribute (see JVMS 4.7.3). */ private static class ExceptionTableEntry { ExceptionTableEntry(Offset startPc, Offset endPc, Offset handlerPc, short catchType) { this.startPc = startPc; this.endPc = endPc; this.handlerPc = handlerPc; this.catchType = catchType; } final Offset startPc, endPc, handlerPc; final short catchType; // 0 == "finally" clause } /** * A class that implements an insertion point into a "Code" attribute. */ public class Inserter extends Offset { @Nullable private Inserter nextInserter; // null == not in "currentInserter" stack } /** * An {@link Offset} who's sole purpose is to later create a 'LineNumberTable' attribute. */ public class LineNumberOffset extends Offset { private final short lineNumber; /** * @param lineNumber 1...65535 */ public LineNumberOffset(int offset, StackMap stackMap, short lineNumber) { this.lineNumber = lineNumber; this.offset = offset; this.setStackMap(stackMap); } } private abstract class Relocatable { /** * Grows the code if the relocation cannot be done without growing code. */ public abstract void grow(); /** * Relocates this object. */ public abstract void relocate(); } /** * A throw-in interface that marks {@link CodeContext.Offset}s as "fix-ups": During the execution of {@link * CodeContext#fixUp}, all "fix-ups" are invoked and can do last touches to the code attribute. *

* This is currently used for inserting the "padding bytes" into the TABLESWITCH and LOOKUPSWITCH instructions. *

*/ public interface FixUp { /** * @see FixUp */ void fixUp(); } /** * @return All the local variables that are allocated in any block in this {@link CodeContext} */ public List getAllLocalVars() { return this.allLocalVars; } /** * Removes all code between from and to. Also removes any {@link CodeContext.Relocatable}s * existing in that range. */ public void removeCode(Offset from, Offset to) { if (from == to) return; int size = to.offset - from.offset; assert size >= 0; if (size == 0) return; // Short circuit. // Shift down the bytecode past "to". System.arraycopy(this.code, to.offset, this.code, from.offset, this.end.offset - to.offset); // Invalidate all offsets between "from" and "to". // Remove all relocatables that originate between "from" and "to". Set invalidOffsets = new HashSet(); { Offset o = from.next; assert o != null; for (; o != to;) { assert o != null; invalidOffsets.add(o); // Invalidate the offset for fast failure. final Offset n = o.next; o.offset = -77; o.prev = null; o.next = null; o = n; assert o != null; } for (;;) { o.offset -= size; if (o == this.end) break; o = o.next; assert o != null; } } // Invalidate all relocatables which originate or target a removed offset. for (Iterator it = this.relocatables.iterator(); it.hasNext();) { Relocatable r = (Relocatable) it.next(); if (r instanceof Branch) { Branch b = (Branch) r; if (invalidOffsets.contains(b.source)) { it.remove(); } else { assert !invalidOffsets.contains(b.destination); } } if (r instanceof OffsetBranch) { OffsetBranch ob = (OffsetBranch) r; if (invalidOffsets.contains(ob.source)) { it.remove(); } else { assert !invalidOffsets.contains(ob.destination); } } } for (Iterator it = this.exceptionTableEntries.iterator(); it.hasNext();) { ExceptionTableEntry ete = (ExceptionTableEntry) it.next(); // Start, end and handler must either ALL lie IN the range to remove or ALL lie outside. if (invalidOffsets.contains(ete.startPc)) { assert invalidOffsets.contains(ete.endPc); assert invalidOffsets.contains(ete.handlerPc); it.remove(); } else { assert !invalidOffsets.contains(ete.endPc); assert !invalidOffsets.contains(ete.handlerPc); } } // Remove local variables in dead-code block. for (Iterator it = this.allLocalVars.iterator(); it.hasNext();) { final Java.LocalVariableSlot var = (Java.LocalVariableSlot) it.next(); if (invalidOffsets.contains(var.getStart())) { assert invalidOffsets.contains(var.getEnd()); it.remove(); } else { assert !invalidOffsets.contains(var.getEnd()); } } from.next = to; to.prev = from; } @Override public String toString() { return this.classFile.getThisClassName() + "/cio=" + this.currentInserter.offset; } // Convenience methods for "pushOperand(VTI)". /** * Pushes one {@code object_variable_info}, {@code integer_variable_info}, {@code double_variable_info}, {@code * float_variable_info} or {@code long_variable_info} entry onto the current inserter's operand stack. */ public void pushOperand(String fieldDescriptor) { if (Descriptor.isReference(fieldDescriptor)) { this.pushObjectOperand(fieldDescriptor); } else if ( fieldDescriptor.equals(Descriptor.BYTE) || fieldDescriptor.equals(Descriptor.CHAR) || fieldDescriptor.equals(Descriptor.INT) || fieldDescriptor.equals(Descriptor.SHORT) || fieldDescriptor.equals(Descriptor.BOOLEAN) ) { this.pushIntOperand(); } else if (fieldDescriptor.equals(Descriptor.DOUBLE)) { this.pushDoubleOperand(); } else if (fieldDescriptor.equals(Descriptor.FLOAT)) { this.pushFloatOperand(); } else if (fieldDescriptor.equals(Descriptor.LONG)) { this.pushLongOperand(); } else { throw new AssertionError(fieldDescriptor); } } public void pushTopOperand() { this.pushOperand(StackMapTableAttribute.TOP_VARIABLE_INFO); } public void pushIntOperand() { this.pushOperand(StackMapTableAttribute.INTEGER_VARIABLE_INFO); } public void pushLongOperand() { this.pushOperand(StackMapTableAttribute.LONG_VARIABLE_INFO); } public void pushFloatOperand() { this.pushOperand(StackMapTableAttribute.FLOAT_VARIABLE_INFO); } public void pushDoubleOperand() { this.pushOperand(StackMapTableAttribute.DOUBLE_VARIABLE_INFO); } public void pushNullOperand() { this.pushOperand(StackMapTableAttribute.NULL_VARIABLE_INFO); } public void pushUninitializedThisOperand() { this.pushOperand(StackMapTableAttribute.UNINITIALIZED_THIS_VARIABLE_INFO); } public void pushUninitializedOperand() { final Offset o = this.newOffset(); final UninitializedVariableInfo uvi = this.classFile.newUninitializedVariableInfo((short) o.offset); this.relocatables.add(new Relocatable() { @Override public void grow() {} @Override public void relocate() { uvi.offset = (short) o.offset; } }); this.pushOperand(uvi); } public void pushObjectOperand(String fieldDescriptor) { this.pushOperand(this.classFile.newObjectVariableInfo(fieldDescriptor)); } public void pushOperand(VerificationTypeInfo topOperand) { final Inserter ci = this.currentInserter(); StackMap sm = ci.getStackMap(); sm = sm.pushOperand(topOperand); ci.setStackMap(sm); int ss = 0; for (VerificationTypeInfo vti : sm.operands()) ss += vti.category(); if (ss > this.maxStack) this.maxStack = ss; } /** * @return Whether the top operand is a {@code null_variable_info} */ public boolean peekNullOperand() { return this.peekOperand() == StackMapTableAttribute.NULL_VARIABLE_INFO; } /** * @return Whether the top operand is a {@code object_variable_info} */ public boolean peekObjectOperand() { return this.peekOperand() instanceof StackMapTableAttribute.ObjectVariableInfo; } /** * @return The verification type of the top operand */ public VerificationTypeInfo peekOperand() { return this.currentInserter().getStackMap().peekOperand(); } /** * Pops one entry from the current inserter's operand stack. */ public VerificationTypeInfo popOperand() { Inserter ci = this.currentInserter(); StackMap sm = ci.getStackMap(); for (;;) { VerificationTypeInfo result = sm.peekOperand(); sm = sm.popOperand(); if (result != StackMapTableAttribute.TOP_VARIABLE_INFO) { ci.setStackMap(sm); return result; } } } /** * Pops the top entry from the operand stack and assert that it equals expected. */ public void popOperand(VerificationTypeInfo expected) { VerificationTypeInfo actual = this.popOperand(); assert actual.equals(expected) : actual; } /** * Pops the top operand, asserts that it is an {@code integer_variable_info}, {@code long_variable_info}, {@code * float_variable_info}, {@code double_variable_info} or {@code variable_object_info}, and asserts that it matches * the given field descriptor. */ public void popOperand(String expectedFd) { VerificationTypeInfo vti = this.popOperand(); if (vti == StackMapTableAttribute.INTEGER_VARIABLE_INFO) { assert ( expectedFd.equals(Descriptor.BOOLEAN) || expectedFd.equals(Descriptor.BYTE) || expectedFd.equals(Descriptor.CHAR) || expectedFd.equals(Descriptor.SHORT) || expectedFd.equals(Descriptor.INT) ) : expectedFd; } else if (vti == StackMapTableAttribute.LONG_VARIABLE_INFO) { assert expectedFd.equals(Descriptor.LONG) : expectedFd; } else if (vti == StackMapTableAttribute.FLOAT_VARIABLE_INFO) { assert expectedFd.equals(Descriptor.FLOAT) : expectedFd; } else if (vti == StackMapTableAttribute.DOUBLE_VARIABLE_INFO) { assert expectedFd.equals(Descriptor.DOUBLE) : expectedFd; } else if (vti == StackMapTableAttribute.NULL_VARIABLE_INFO) { assert expectedFd.equals(Descriptor.VOID) : expectedFd; } else if (vti instanceof StackMapTableAttribute.ObjectVariableInfo) { assert Descriptor.isReference(expectedFd) : expectedFd + " vs. " + vti; final ObjectVariableInfo ovi = (StackMapTableAttribute.ObjectVariableInfo) vti; final ConstantClassInfo cci = this.classFile.getConstantClassInfo(ovi.getConstantClassInfoIndex()); final String computationalTypeFd = Descriptor.fromInternalForm(cci.getName(this.classFile)); assert expectedFd.equals(computationalTypeFd) : expectedFd + " vs. " + computationalTypeFd; } else if (vti instanceof StackMapTableAttribute.UninitializedVariableInfo) { assert Descriptor.isReference(expectedFd) : expectedFd; } else { throw new AssertionError(vti); } } public void popOperandAssignableTo(String declaredFd) { if (Descriptor.isPrimitive(declaredFd)) { this.popOperand(declaredFd); } else { this.popObjectOrUninitializedOrUninitializedThisOperand(); } } /** * Asserts that the top operand is an {@code integer_variable_info} and pops it. */ public void popIntOperand() { this.popOperand(StackMapTableAttribute.INTEGER_VARIABLE_INFO); } /** * Asserts that the top operand is a {@code long_variable_info} and pops it. */ public void popLongOperand() { this.popOperand(StackMapTableAttribute.LONG_VARIABLE_INFO); } /** * Asserts that the top operand is an {@code uninitializedThis_variable_info} and pops it. */ public void popUninitializedThisOperand() { this.popOperand(StackMapTableAttribute.UNINITIALIZED_THIS_VARIABLE_INFO); } /** * Asserts that the top operand is an {@code uninitialized_variable_info} and pops it. */ public void popUninitializedVariableOperand() { final VerificationTypeInfo op = this.popOperand(); assert op instanceof StackMapTableAttribute.UninitializedVariableInfo : String.valueOf(op); } /** * Asserts that the top operand is an {@code object_variable_info} or a {@code null_variable_info} and pops it. */ public void popReferenceOperand() { assert this.peekObjectOperand() || this.peekNullOperand() : this.peekOperand(); this.popOperand(); } /** * Asserts that the top operand is an {@code object_variable_info}, and pops it. * * @return The field descriptor of the popped object operand */ public String popObjectOperand() { VerificationTypeInfo vti = this.popOperand(); assert vti instanceof StackMapTableAttribute.ObjectVariableInfo : vti; final ObjectVariableInfo ovi = (StackMapTableAttribute.ObjectVariableInfo) vti; short ccii = ovi.getConstantClassInfoIndex(); ConstantClassInfo cci = this.classFile.getConstantClassInfo(ccii); return Descriptor.fromInternalForm(cci.getName(this.classFile)); } /** * Asserts that the top operand is an {@code object_variable_info}, {@code uninitialized_variable_info} or * {@code uninitializedThis_variable_info}, and pops it. */ public VerificationTypeInfo popObjectOrUninitializedOrUninitializedThisOperand() { VerificationTypeInfo result = this.popOperand(); assert ( result instanceof StackMapTableAttribute.UninitializedVariableInfo || result instanceof StackMapTableAttribute.ObjectVariableInfo || result == StackMapTableAttribute.UNINITIALIZED_THIS_VARIABLE_INFO ) : result; return result; } /** * Asserts that the top operand is an {@code int_variable_info} or {@code long_variable_info}, then pops and * returns it. */ public VerificationTypeInfo popIntOrLongOperand() { VerificationTypeInfo result = this.popOperand(); assert ( result == StackMapTableAttribute.INTEGER_VARIABLE_INFO || result == StackMapTableAttribute.LONG_VARIABLE_INFO ) : result; return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy