1372 lines
51 KiB
Java
1372 lines
51 KiB
Java
|
|
/*
|
|
* Janino - An embedded Java[TM] compiler
|
|
*
|
|
* Copyright (c) 2001-2010, Arno Unkrig
|
|
* All rights reserved.
|
|
*
|
|
* 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. The name of the author may not be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.janino.util.ClassFile;
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
@SuppressWarnings({ "rawtypes", "unchecked" }) public
|
|
class CodeContext {
|
|
private static final boolean DEBUG = false;
|
|
|
|
private static final int INITIAL_SIZE = 128;
|
|
private static final byte UNEXAMINED = -1;
|
|
private static final byte INVALID_OFFSET = -2;
|
|
private static final int MAX_STACK_SIZE = 254;
|
|
|
|
private final ClassFile classFile;
|
|
private final String functionName;
|
|
|
|
private short maxStack;
|
|
private short maxLocals;
|
|
private byte[] code;
|
|
private final Offset beginning;
|
|
private final Inserter end;
|
|
private Inserter currentInserter;
|
|
private final List<ExceptionTableEntry> exceptionTableEntries;
|
|
|
|
/** All the local variables that are allocated in any block in this {@link CodeContext}. */
|
|
private final List<Java.LocalVariableSlot> allLocalVars = new ArrayList();
|
|
|
|
/**
|
|
* List of List of Java.LocalVariableSlot objects. Each List of Java.LocalVariableSlot is
|
|
* the local variables allocated for a block. They are pushed and poped onto the list together
|
|
* to make allocation of the next local variable slot easy.
|
|
*/
|
|
private final List<List<Java.LocalVariableSlot>> scopedVars = new ArrayList();
|
|
|
|
private short nextLocalVariableSlot;
|
|
private final List<Relocatable> relocatables = new ArrayList();
|
|
|
|
/** Creates an empty "Code" attribute. */
|
|
public
|
|
CodeContext(ClassFile classFile, String functionName) {
|
|
this.classFile = classFile;
|
|
this.functionName = functionName;
|
|
|
|
this.maxStack = 0;
|
|
this.maxLocals = 0;
|
|
this.code = new byte[CodeContext.INITIAL_SIZE];
|
|
this.beginning = new Offset();
|
|
this.end = new Inserter();
|
|
this.currentInserter = this.end;
|
|
this.exceptionTableEntries = new ArrayList();
|
|
|
|
this.beginning.offset = 0;
|
|
this.end.offset = 0;
|
|
this.beginning.next = this.end;
|
|
this.end.prev = this.beginning;
|
|
}
|
|
|
|
/** The {@link ClassFile} this context is related to. */
|
|
public ClassFile
|
|
getClassFile() { return this.classFile; }
|
|
|
|
|
|
/**
|
|
* Allocate 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(); }
|
|
|
|
/**
|
|
* Allocate 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 it's null, the variable won't be written to the localvariabletable
|
|
* @param type The variable type. if the name isn't null, the type is needed to write to the localvariabletable
|
|
*/
|
|
public Java.LocalVariableSlot
|
|
allocateLocalVariable(short size, String name, IClass type) {
|
|
List<Java.LocalVariableSlot> currentVars = null;
|
|
|
|
if (this.scopedVars.size() == 0) {
|
|
throw new Error("saveLocalVariables must be called first");
|
|
} else {
|
|
currentVars = (List<Java.LocalVariableSlot>) this.scopedVars.get(this.scopedVars.size() - 1);
|
|
}
|
|
|
|
Java.LocalVariableSlot slot = new Java.LocalVariableSlot(name, this.nextLocalVariableSlot, type);
|
|
|
|
if (slot.getName() != null) {
|
|
slot.setStart(this.newOffset());
|
|
}
|
|
|
|
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<Java.LocalVariableSlot>
|
|
saveLocalVariables() {
|
|
|
|
// Push empty list on the stack to hold a new block's local vars.
|
|
List<Java.LocalVariableSlot> l = new ArrayList();
|
|
this.scopedVars.add(l);
|
|
|
|
return l;
|
|
}
|
|
|
|
/**
|
|
* Restore 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.
|
|
List<Java.LocalVariableSlot> slots = (
|
|
(List<Java.LocalVariableSlot>) this.scopedVars.remove(this.scopedVars.size() - 1)
|
|
);
|
|
for (Java.LocalVariableSlot slot : slots) {
|
|
if (slot.getName() != null) {
|
|
slot.setEnd(this.newOffset());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param dos
|
|
* @param lineNumberTableAttributeNameIndex 0 == don't generate a "LineNumberTable" attribute
|
|
* @throws IOException
|
|
*/
|
|
protected void
|
|
storeCodeAttributeBody(
|
|
DataOutputStream dos,
|
|
short lineNumberTableAttributeNameIndex,
|
|
short localVariableTableAttributeNameIndex
|
|
) 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<ClassFile.AttributeInfo> attributes = new ArrayList();
|
|
|
|
// Add "LineNumberTable" attribute.
|
|
if (lineNumberTableAttributeNameIndex != 0) {
|
|
List<ClassFile.LineNumberTableAttribute.Entry> lnt = new ArrayList();
|
|
for (Offset o = this.beginning; o != null; o = o.next) {
|
|
if (o instanceof LineNumberOffset) {
|
|
lnt.add(new ClassFile.LineNumberTableAttribute.Entry(o.offset, ((LineNumberOffset) o).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);
|
|
}
|
|
|
|
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}
|
|
*/
|
|
protected ClassFile.AttributeInfo
|
|
storeLocalVariableTable(DataOutputStream dos, short localVariableTableAttributeNameIndex) {
|
|
ClassFile cf = this.getClassFile();
|
|
final List<ClassFile.LocalVariableTableAttribute.Entry> entryList = new ArrayList();
|
|
|
|
for (Java.LocalVariableSlot slot : this.getAllLocalVars()) {
|
|
|
|
if (slot.getName() != null) {
|
|
String typeName = slot.getType().getDescriptor();
|
|
short classSlot = cf.addConstantUtf8Info(typeName);
|
|
short varNameSlot = cf.addConstantUtf8Info(slot.getName());
|
|
|
|
// System.out.println("slot: " + slot + ", typeSlot: " + classSlot + ", varSlot: " + varNameSlot);
|
|
|
|
ClassFile.LocalVariableTableAttribute.Entry entry = new ClassFile.LocalVariableTableAttribute.Entry(
|
|
(short) slot.getStart().offset,
|
|
(short) (slot.getEnd().offset - slot.getStart().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
|
|
);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks the code for consistency; updates the "maxStack" member.
|
|
*
|
|
* Notice: On inconsistencies, a "RuntimeException" is thrown (KLUDGE).
|
|
*/
|
|
public void
|
|
flowAnalysis(String functionName) {
|
|
if (CodeContext.DEBUG) {
|
|
System.err.println("flowAnalysis(" + functionName + ")");
|
|
}
|
|
|
|
short[] stackSizes = new short[this.end.offset];
|
|
Arrays.fill(stackSizes, CodeContext.UNEXAMINED);
|
|
|
|
// Analyze flow from offset zero.
|
|
this.flowAnalysis(
|
|
functionName,
|
|
this.code, // code
|
|
this.end.offset, // codeSize
|
|
0, // offset
|
|
(short) 0, // stackSize
|
|
stackSizes // stackSizes
|
|
);
|
|
|
|
// Analyze flow from exception handler entry points.
|
|
int analyzedExceptionHandlers = 0;
|
|
while (analyzedExceptionHandlers != this.exceptionTableEntries.size()) {
|
|
for (ExceptionTableEntry exceptionTableEntry : this.exceptionTableEntries) {
|
|
if (stackSizes[exceptionTableEntry.startPC.offset] != CodeContext.UNEXAMINED) {
|
|
this.flowAnalysis(
|
|
functionName,
|
|
this.code, // code
|
|
this.end.offset, // codeSize
|
|
exceptionTableEntry.handlerPC.offset, // offset
|
|
(short) (stackSizes[exceptionTableEntry.startPC.offset] + 1), // stackSize
|
|
stackSizes // stackSizes
|
|
);
|
|
++analyzedExceptionHandlers;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check results and determine maximum stack size.
|
|
this.maxStack = 0;
|
|
for (int i = 0; i < stackSizes.length; ++i) {
|
|
short ss = stackSizes[i];
|
|
if (ss == CodeContext.UNEXAMINED) {
|
|
if (CodeContext.DEBUG) {
|
|
System.out.println(functionName + ": Unexamined code at offset " + i);
|
|
return;
|
|
} else {
|
|
throw new JaninoRuntimeException(functionName + ": Unexamined code at offset " + i);
|
|
}
|
|
}
|
|
if (ss > this.maxStack) this.maxStack = ss;
|
|
}
|
|
}
|
|
|
|
private void
|
|
flowAnalysis(
|
|
String functionName,
|
|
byte[] code, // Bytecode
|
|
int codeSize, // Size
|
|
int offset, // Current PC
|
|
short stackSize, // Stack size on entry
|
|
short[] stackSizes // Stack sizes in code
|
|
) {
|
|
for (;;) {
|
|
if (CodeContext.DEBUG) System.out.println("Offset = " + offset + ", stack size = " + stackSize);
|
|
|
|
// Check current bytecode offset.
|
|
if (offset < 0 || offset >= codeSize) {
|
|
throw new JaninoRuntimeException(functionName + ": Offset out of range");
|
|
}
|
|
|
|
// Have we hit an area that has already been analyzed?
|
|
int css = stackSizes[offset];
|
|
if (css == stackSize) return; // OK.
|
|
if (css == CodeContext.INVALID_OFFSET) throw new JaninoRuntimeException(functionName + ": Invalid offset");
|
|
if (css != CodeContext.UNEXAMINED) {
|
|
if (CodeContext.DEBUG) {
|
|
System.err.println(
|
|
functionName
|
|
+ ": Operand stack inconsistent at offset "
|
|
+ offset
|
|
+ ": Previous size "
|
|
+ css
|
|
+ ", now "
|
|
+ stackSize
|
|
);
|
|
return;
|
|
} else {
|
|
throw new JaninoRuntimeException(
|
|
functionName
|
|
+ ": Operand stack inconsistent at offset "
|
|
+ offset
|
|
+ ": Previous size "
|
|
+ css
|
|
+ ", now "
|
|
+ stackSize
|
|
);
|
|
}
|
|
}
|
|
stackSizes[offset] = stackSize;
|
|
|
|
// Analyze current opcode.
|
|
byte opcode = code[offset];
|
|
int operandOffset = offset + 1;
|
|
short props;
|
|
if (opcode == Opcode.WIDE) {
|
|
opcode = code[operandOffset++];
|
|
props = Opcode.WIDE_OPCODE_PROPERTIES[0xff & opcode];
|
|
} else {
|
|
props = Opcode.OPCODE_PROPERTIES[0xff & opcode];
|
|
}
|
|
if (props == Opcode.INVALID_OPCODE) {
|
|
throw new JaninoRuntimeException(
|
|
functionName
|
|
+ ": Invalid opcode "
|
|
+ (0xff & opcode)
|
|
+ " at offset "
|
|
+ offset
|
|
);
|
|
}
|
|
|
|
switch (props & Opcode.SD_MASK) {
|
|
|
|
case Opcode.SD_M4:
|
|
case Opcode.SD_M3:
|
|
case Opcode.SD_M2:
|
|
case Opcode.SD_M1:
|
|
case Opcode.SD_P0:
|
|
case Opcode.SD_P1:
|
|
case Opcode.SD_P2:
|
|
stackSize += (props & Opcode.SD_MASK) - Opcode.SD_P0;
|
|
break;
|
|
|
|
case Opcode.SD_0:
|
|
stackSize = 0;
|
|
break;
|
|
|
|
case Opcode.SD_GETFIELD:
|
|
--stackSize;
|
|
/* FALL THROUGH */
|
|
case Opcode.SD_GETSTATIC:
|
|
stackSize += this.determineFieldSize((short) (
|
|
CodeContext.extract16BitValue(0, operandOffset, code)
|
|
));
|
|
break;
|
|
|
|
case Opcode.SD_PUTFIELD:
|
|
--stackSize;
|
|
/* FALL THROUGH */
|
|
case Opcode.SD_PUTSTATIC:
|
|
stackSize -= this.determineFieldSize((short) (
|
|
CodeContext.extract16BitValue(0, operandOffset, code)
|
|
));
|
|
break;
|
|
|
|
case Opcode.SD_INVOKEVIRTUAL:
|
|
case Opcode.SD_INVOKESPECIAL:
|
|
case Opcode.SD_INVOKEINTERFACE:
|
|
--stackSize;
|
|
/* FALL THROUGH */
|
|
case Opcode.SD_INVOKESTATIC:
|
|
stackSize -= this.determineArgumentsSize((short) (
|
|
CodeContext.extract16BitValue(0, operandOffset, code)
|
|
));
|
|
break;
|
|
|
|
case Opcode.SD_MULTIANEWARRAY:
|
|
stackSize -= code[operandOffset + 2] - 1;
|
|
break;
|
|
|
|
default:
|
|
throw new JaninoRuntimeException(functionName + ": Invalid stack delta");
|
|
}
|
|
|
|
if (stackSize < 0) {
|
|
String msg = (
|
|
this.classFile.getThisClassName()
|
|
+ '.'
|
|
+ functionName
|
|
+ ": Operand stack underrun at offset "
|
|
+ offset
|
|
);
|
|
if (CodeContext.DEBUG) {
|
|
System.err.println(msg);
|
|
return;
|
|
} else {
|
|
throw new JaninoRuntimeException(msg);
|
|
}
|
|
}
|
|
|
|
if (stackSize > CodeContext.MAX_STACK_SIZE) {
|
|
String msg = (
|
|
this.classFile.getThisClassName()
|
|
+ '.'
|
|
+ functionName
|
|
+ ": Operand stack overflow at offset "
|
|
+ offset
|
|
);
|
|
if (CodeContext.DEBUG) {
|
|
System.err.println(msg);
|
|
return;
|
|
} else {
|
|
throw new JaninoRuntimeException(msg);
|
|
}
|
|
}
|
|
|
|
switch (props & Opcode.OP1_MASK) {
|
|
|
|
case 0:
|
|
;
|
|
break;
|
|
|
|
case Opcode.OP1_SB:
|
|
case Opcode.OP1_UB:
|
|
case Opcode.OP1_CP1:
|
|
case Opcode.OP1_LV1:
|
|
++operandOffset;
|
|
break;
|
|
|
|
case Opcode.OP1_SS:
|
|
case Opcode.OP1_CP2:
|
|
case Opcode.OP1_LV2:
|
|
operandOffset += 2;
|
|
break;
|
|
|
|
case Opcode.OP1_BO2:
|
|
if (CodeContext.DEBUG) {
|
|
System.out.println("Offset = " + offset);
|
|
System.out.println("Operand offset = " + operandOffset);
|
|
System.out.println(code[operandOffset]);
|
|
System.out.println(code[operandOffset + 1]);
|
|
}
|
|
this.flowAnalysis(
|
|
functionName,
|
|
code, codeSize,
|
|
CodeContext.extract16BitValue(offset, operandOffset, code),
|
|
stackSize,
|
|
stackSizes
|
|
);
|
|
operandOffset += 2;
|
|
break;
|
|
|
|
case Opcode.OP1_JSR:
|
|
if (CodeContext.DEBUG) {
|
|
System.out.println("Offset = " + offset);
|
|
System.out.println("Operand offset = " + operandOffset);
|
|
System.out.println(code[operandOffset]);
|
|
System.out.println(code[operandOffset + 1]);
|
|
}
|
|
int targetOffset = CodeContext.extract16BitValue(offset, operandOffset, code);
|
|
operandOffset += 2;
|
|
if (stackSizes[targetOffset] == CodeContext.UNEXAMINED) {
|
|
this.flowAnalysis(
|
|
functionName,
|
|
code, codeSize,
|
|
targetOffset,
|
|
(short) (stackSize + 1),
|
|
stackSizes
|
|
);
|
|
}
|
|
break;
|
|
|
|
case Opcode.OP1_BO4:
|
|
this.flowAnalysis(
|
|
functionName,
|
|
code, codeSize,
|
|
CodeContext.extract32BitValue(offset, operandOffset, code),
|
|
stackSize, stackSizes
|
|
);
|
|
operandOffset += 4;
|
|
break;
|
|
|
|
case Opcode.OP1_LOOKUPSWITCH:
|
|
while ((operandOffset & 3) != 0) ++operandOffset;
|
|
this.flowAnalysis(
|
|
functionName,
|
|
code, codeSize,
|
|
CodeContext.extract32BitValue(offset, operandOffset, code),
|
|
stackSize, stackSizes
|
|
);
|
|
operandOffset += 4;
|
|
|
|
int npairs = CodeContext.extract32BitValue(0, operandOffset, code);
|
|
operandOffset += 4;
|
|
|
|
for (int i = 0; i < npairs; ++i) {
|
|
operandOffset += 4; //skip match value
|
|
this.flowAnalysis(
|
|
functionName,
|
|
code, codeSize,
|
|
CodeContext.extract32BitValue(offset, operandOffset, code),
|
|
stackSize, stackSizes
|
|
);
|
|
operandOffset += 4; //advance over offset
|
|
}
|
|
break;
|
|
|
|
case Opcode.OP1_TABLESWITCH:
|
|
while ((operandOffset & 3) != 0) ++operandOffset;
|
|
this.flowAnalysis(
|
|
functionName,
|
|
code, codeSize,
|
|
CodeContext.extract32BitValue(offset, operandOffset, code),
|
|
stackSize, stackSizes
|
|
);
|
|
operandOffset += 4;
|
|
int low = CodeContext.extract32BitValue(offset, operandOffset, code);
|
|
operandOffset += 4;
|
|
int hi = CodeContext.extract32BitValue(offset, operandOffset, code);
|
|
operandOffset += 4;
|
|
for (int i = low; i <= hi; ++i) {
|
|
this.flowAnalysis(
|
|
functionName,
|
|
code, codeSize,
|
|
CodeContext.extract32BitValue(offset, operandOffset, code),
|
|
stackSize, stackSizes
|
|
);
|
|
operandOffset += 4;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new JaninoRuntimeException(functionName + ": Invalid OP1");
|
|
}
|
|
|
|
switch (props & Opcode.OP2_MASK) {
|
|
|
|
case 0:
|
|
;
|
|
break;
|
|
|
|
case Opcode.OP2_SB:
|
|
++operandOffset;
|
|
break;
|
|
|
|
case Opcode.OP2_SS:
|
|
operandOffset += 2;
|
|
break;
|
|
|
|
default:
|
|
throw new JaninoRuntimeException(functionName + ": Invalid OP2");
|
|
}
|
|
|
|
switch (props & Opcode.OP3_MASK) {
|
|
|
|
case 0:
|
|
;
|
|
break;
|
|
|
|
case Opcode.OP3_SB:
|
|
++operandOffset;
|
|
break;
|
|
|
|
default:
|
|
throw new JaninoRuntimeException(functionName + ": Invalid OP3");
|
|
}
|
|
|
|
Arrays.fill(stackSizes, offset + 1, operandOffset, CodeContext.INVALID_OFFSET);
|
|
|
|
if ((props & Opcode.NO_FALLTHROUGH) != 0) return;
|
|
offset = operandOffset;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract a 16 bit value at offset in code and add bias to it
|
|
*
|
|
* @param bias An int to skew the final result by (useful for calculating relative offsets)
|
|
* @param offset The position in the code array to extract the bytes from
|
|
* @param code The array of bytes
|
|
* @return An integer that treats the two bytes at position offset as an UNSIGNED SHORT
|
|
*/
|
|
private static int
|
|
extract16BitValue(int bias, int offset, byte[] code) {
|
|
int res = bias + (
|
|
((code[offset]) << 8)
|
|
+ (code[offset + 1] & 0xff)
|
|
);
|
|
if (CodeContext.DEBUG) {
|
|
System.out.println("extract16BitValue(bias, offset) = (" + bias + ", " + offset + ")");
|
|
System.out.println("bytes = {" + code[offset] + ", " + code[offset + 1] + "}");
|
|
System.out.println("result = " + res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Extract a 32 bit value at offset in code and add bias to it
|
|
*
|
|
* @param bias An int to skew the final result by (useful for calculating relative offsets)
|
|
* @param offset The position in the code array to extract the bytes from
|
|
* @param code The array of bytes
|
|
* @return The 4 bytes at position offset + bias
|
|
*/
|
|
private static int
|
|
extract32BitValue(int bias, int offset, byte[] code) {
|
|
int res = bias + (
|
|
(code[offset] << 24)
|
|
+ ((0xff & code[offset + 1]) << 16)
|
|
+ ((0xff & code[offset + 2]) << 8)
|
|
+ (0xff & code[offset + 3])
|
|
);
|
|
if (CodeContext.DEBUG) {
|
|
System.out.println("extract32BitValue(bias, offset) = (" + bias + ", " + offset + ")");
|
|
System.out.println(
|
|
""
|
|
+ "bytes = {"
|
|
+ code[offset]
|
|
+ ", "
|
|
+ code[offset + 1]
|
|
+ ", "
|
|
+ code[offset + 2]
|
|
+ ", "
|
|
+ code[offset + 3]
|
|
+ "}"
|
|
);
|
|
System.out.println("result = " + res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/** Fixes up all of the offsets and relocate() all relocatables. */
|
|
public void
|
|
fixUpAndRelocate() {
|
|
|
|
// We do this in a loop to allow relocatables to adjust the size
|
|
// of things in the byte stream. It is extremely unlikely, but possible
|
|
// that a late relocatable will grow the size of the bytecode, and require
|
|
// an earlier relocatable to switch from 32K mode to 64K mode branching
|
|
do {
|
|
this.fixUp();
|
|
} while (!this.relocate());
|
|
}
|
|
|
|
/** Fixes up all offsets. */
|
|
private void
|
|
fixUp() {
|
|
for (Offset o = this.beginning; o != this.end; o = o.next) {
|
|
if (o instanceof FixUp) ((FixUp) o).fixUp();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Relocate all relocatables and aggregate their response into a single one
|
|
* @return true if all of them relocated successfully
|
|
* false if any of them needed to change size
|
|
*/
|
|
private boolean
|
|
relocate() {
|
|
boolean finished = true;
|
|
for (Relocatable relocatable : this.relocatables) {
|
|
|
|
// Do not terminate earlier so that everything gets a chance to grow in the first pass changes the common
|
|
// case for this to be O(n) instead of O(n**2).
|
|
finished &= relocatable.relocate();
|
|
}
|
|
return finished;
|
|
}
|
|
|
|
/** Analyses the descriptor of the Fieldref and return its size. */
|
|
private int
|
|
determineFieldSize(short idx) {
|
|
ClassFile.ConstantFieldrefInfo cfi = (
|
|
(ClassFile.ConstantFieldrefInfo) this.classFile.getConstantPoolInfo(idx)
|
|
);
|
|
return Descriptor.size(cfi.getNameAndType(this.classFile).getDescriptor(this.classFile));
|
|
}
|
|
|
|
/**
|
|
* Analyse the descriptor of the Methodref and return the sum of the
|
|
* arguments' sizes minus the return value's size.
|
|
*/
|
|
private int
|
|
determineArgumentsSize(short idx) {
|
|
ClassFile.ConstantPoolInfo cpi = this.classFile.getConstantPoolInfo(idx);
|
|
ClassFile.ConstantNameAndTypeInfo nat = (
|
|
cpi instanceof ClassFile.ConstantInterfaceMethodrefInfo
|
|
? ((ClassFile.ConstantInterfaceMethodrefInfo) cpi).getNameAndType(this.classFile)
|
|
: ((ClassFile.ConstantMethodrefInfo) cpi).getNameAndType(this.classFile)
|
|
);
|
|
String desc = nat.getDescriptor(this.classFile);
|
|
|
|
if (desc.charAt(0) != '(') throw new JaninoRuntimeException("Method descriptor does not start with \"(\"");
|
|
int i = 1;
|
|
int res = 0;
|
|
for (;;) {
|
|
switch (desc.charAt(i++)) {
|
|
case ')':
|
|
return res - Descriptor.size(desc.substring(i));
|
|
case 'B': case 'C': case 'F': case 'I': case 'S': case 'Z':
|
|
res += 1;
|
|
break;
|
|
case 'D': case 'J':
|
|
res += 2;
|
|
break;
|
|
case '[':
|
|
res += 1;
|
|
while (desc.charAt(i) == '[') ++i;
|
|
if ("BCFISZDJ".indexOf(desc.charAt(i)) != -1) { ++i; break; }
|
|
if (desc.charAt(i) != 'L') throw new JaninoRuntimeException("Invalid char after \"[\"");
|
|
++i;
|
|
while (desc.charAt(i++) != ';');
|
|
break;
|
|
case 'L':
|
|
res += 1;
|
|
while (desc.charAt(i++) != ';');
|
|
break;
|
|
default:
|
|
throw new JaninoRuntimeException("Invalid method descriptor");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inserts a sequence of bytes at the current insertion position. Creates
|
|
* {@link LineNumberOffset}s as necessary.
|
|
*
|
|
* @param lineNumber The line number that corresponds to the byte code, or -1
|
|
* @param b
|
|
*/
|
|
public void
|
|
write(short lineNumber, byte[] b) {
|
|
if (b.length == 0) return;
|
|
|
|
int ico = this.currentInserter.offset;
|
|
this.makeSpace(lineNumber, b.length);
|
|
System.arraycopy(b, 0, this.code, ico, b.length);
|
|
}
|
|
|
|
/**
|
|
* Inserts a byte at the current insertion position. Creates
|
|
* {@link LineNumberOffset}s as necessary.
|
|
* <p>
|
|
* This method is an optimization to avoid allocating small byte[] and ease
|
|
* GC load.
|
|
*
|
|
* @param lineNumber The line number that corresponds to the byte code, or -1
|
|
* @param b1
|
|
*/
|
|
public void
|
|
write(short lineNumber, byte b1) {
|
|
int ico = this.currentInserter.offset;
|
|
this.makeSpace(lineNumber, 1);
|
|
this.code[ico] = b1;
|
|
}
|
|
|
|
/**
|
|
* Inserts bytes at the current insertion position. Creates
|
|
* {@link LineNumberOffset}s as necessary.
|
|
* <p>
|
|
* This method is an optimization to avoid allocating small byte[] and ease
|
|
* GC load.
|
|
*
|
|
* @param lineNumber The line number that corresponds to the byte code, or -1
|
|
* @param b1
|
|
* @param b2
|
|
*/
|
|
public void
|
|
write(short lineNumber, byte b1, byte b2) {
|
|
int ico = this.currentInserter.offset;
|
|
this.makeSpace(lineNumber, 2);
|
|
this.code[ico++] = b1;
|
|
this.code[ico] = b2;
|
|
}
|
|
|
|
/**
|
|
* Inserts bytes at the current insertion position. Creates
|
|
* {@link LineNumberOffset}s as necessary.
|
|
* <p>
|
|
* This method is an optimization to avoid allocating small byte[] and ease
|
|
* GC load.
|
|
*
|
|
* @param lineNumber The line number that corresponds to the byte code, or -1
|
|
* @param b1
|
|
* @param b2
|
|
* @param b3
|
|
*/
|
|
public void
|
|
write(short lineNumber, byte b1, byte b2, byte b3) {
|
|
int ico = this.currentInserter.offset;
|
|
this.makeSpace(lineNumber, 3);
|
|
this.code[ico++] = b1;
|
|
this.code[ico++] = b2;
|
|
this.code[ico] = b3;
|
|
}
|
|
|
|
/**
|
|
* Inserts bytes at the current insertion position. Creates
|
|
* {@link LineNumberOffset}s as necessary.
|
|
* <p>
|
|
* This method is an optimization to avoid allocating small byte[] and ease
|
|
* GC load.
|
|
*
|
|
* @param lineNumber The line number that corresponds to the byte code, or -1
|
|
* @param b1
|
|
* @param b2
|
|
* @param b3
|
|
* @param b4
|
|
*/
|
|
public void
|
|
write(short lineNumber, byte b1, byte b2, byte b3, byte b4) {
|
|
int ico = this.currentInserter.offset;
|
|
this.makeSpace(lineNumber, 4);
|
|
this.code[ico++] = b1;
|
|
this.code[ico++] = b2;
|
|
this.code[ico++] = b3;
|
|
this.code[ico] = b4;
|
|
}
|
|
|
|
/**
|
|
* Add space for {@code size} bytes at current offset. Creates {@link LineNumberOffset}s as necessary.
|
|
*
|
|
* @param lineNumber The line number that corresponds to the byte code, or -1
|
|
* @param size The size in bytes to inject
|
|
*/
|
|
public void
|
|
makeSpace(short lineNumber, int size) {
|
|
if (size == 0) return;
|
|
|
|
INSERT_LINE_NUMBER_OFFSET:
|
|
if (lineNumber != -1) {
|
|
Offset o;
|
|
for (o = this.currentInserter.prev; o != this.beginning; o = o.prev) {
|
|
if (o instanceof LineNumberOffset) {
|
|
if (((LineNumberOffset) o).lineNumber == lineNumber) break INSERT_LINE_NUMBER_OFFSET;
|
|
break;
|
|
}
|
|
}
|
|
LineNumberOffset lno = new LineNumberOffset(this.currentInserter.offset, lineNumber);
|
|
lno.prev = this.currentInserter.prev;
|
|
lno.next = this.currentInserter;
|
|
|
|
this.currentInserter.prev.next = lno;
|
|
this.currentInserter.prev = lno;
|
|
}
|
|
|
|
int ico = this.currentInserter.offset;
|
|
if (this.end.offset + size <= this.code.length) {
|
|
// Optimization to avoid a trivial method call in the common case
|
|
if (ico != this.end.offset) {
|
|
System.arraycopy(this.code, ico, this.code, ico + size, this.end.offset - ico);
|
|
}
|
|
} 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 JaninoRuntimeException(
|
|
"Code of method \""
|
|
+ this.functionName
|
|
+ "\" of class \""
|
|
+ this.classFile.getThisClassName()
|
|
+ "\" grows beyond 64 KB"
|
|
);
|
|
}
|
|
this.code = new byte[newSize];
|
|
System.arraycopy(oldCode, 0, this.code, 0, ico);
|
|
System.arraycopy(oldCode, ico, this.code, ico + size, this.end.offset - ico);
|
|
}
|
|
Arrays.fill(this.code, ico, ico + size, (byte) 0);
|
|
for (Offset o = this.currentInserter; o != null; o = o.next) o.offset += size;
|
|
}
|
|
|
|
/** @param lineNumber The line number that corresponds to the byte code, or -1 */
|
|
public void
|
|
writeShort(short lineNumber, int v) { this.write(lineNumber, (byte) (v >> 8), (byte) v); }
|
|
|
|
/** @param lineNumber The line number that corresponds to the byte code, or -1 */
|
|
public void
|
|
writeBranch(short lineNumber, int opcode, final Offset dst) {
|
|
this.relocatables.add(new Branch(opcode, dst));
|
|
this.write(lineNumber, (byte) opcode, (byte) -1, (byte) -1);
|
|
}
|
|
|
|
private
|
|
class Branch extends Relocatable {
|
|
|
|
public
|
|
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 boolean
|
|
relocate() {
|
|
if (this.destination.offset == Offset.UNSET) {
|
|
throw new JaninoRuntimeException("Cannot relocate branch to unset destination offset");
|
|
}
|
|
int offset = this.destination.offset - this.source.offset;
|
|
|
|
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
|
|
CodeContext.this.makeSpace(
|
|
(short) -1,
|
|
this.opcode == Opcode.GOTO ? 2 : this.opcode == Opcode.JSR ? 2 : 5
|
|
);
|
|
}
|
|
CodeContext.this.popInserter();
|
|
this.source.offset = pos;
|
|
this.expanded = true;
|
|
return false;
|
|
}
|
|
|
|
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 == Opcode.JSR) {
|
|
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[] {
|
|
CodeContext.invertBranchOpcode((byte) this.opcode),
|
|
0,
|
|
8, // Jump from this instruction past the GOTO_W
|
|
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);
|
|
return true;
|
|
}
|
|
|
|
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 byte
|
|
invertBranchOpcode(byte branchOpcode) {
|
|
return ((Byte) CodeContext.BRANCH_OPCODE_INVERSION.get(new Byte(branchOpcode))).byteValue();
|
|
}
|
|
|
|
private static final Map<Byte /*branch-opcode*/, Byte /*inverted-branch-opcode*/>
|
|
BRANCH_OPCODE_INVERSION = CodeContext.createBranchOpcodeInversion();
|
|
private static Map<Byte, Byte>
|
|
createBranchOpcodeInversion() {
|
|
Map<Byte, Byte> m = new HashMap();
|
|
m.put(new Byte(Opcode.IF_ACMPEQ), new Byte(Opcode.IF_ACMPNE));
|
|
m.put(new Byte(Opcode.IF_ACMPNE), new Byte(Opcode.IF_ACMPEQ));
|
|
m.put(new Byte(Opcode.IF_ICMPEQ), new Byte(Opcode.IF_ICMPNE));
|
|
m.put(new Byte(Opcode.IF_ICMPNE), new Byte(Opcode.IF_ICMPEQ));
|
|
m.put(new Byte(Opcode.IF_ICMPGE), new Byte(Opcode.IF_ICMPLT));
|
|
m.put(new Byte(Opcode.IF_ICMPLT), new Byte(Opcode.IF_ICMPGE));
|
|
m.put(new Byte(Opcode.IF_ICMPGT), new Byte(Opcode.IF_ICMPLE));
|
|
m.put(new Byte(Opcode.IF_ICMPLE), new Byte(Opcode.IF_ICMPGT));
|
|
m.put(new Byte(Opcode.IFEQ), new Byte(Opcode.IFNE));
|
|
m.put(new Byte(Opcode.IFNE), new Byte(Opcode.IFEQ));
|
|
m.put(new Byte(Opcode.IFGE), new Byte(Opcode.IFLT));
|
|
m.put(new Byte(Opcode.IFLT), new Byte(Opcode.IFGE));
|
|
m.put(new Byte(Opcode.IFGT), new Byte(Opcode.IFLE));
|
|
m.put(new Byte(Opcode.IFLE), new Byte(Opcode.IFGT));
|
|
m.put(new Byte(Opcode.IFNULL), new Byte(Opcode.IFNONNULL));
|
|
m.put(new Byte(Opcode.IFNONNULL), new Byte(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(short lineNumber, Offset src, final Offset dst) {
|
|
this.relocatables.add(new OffsetBranch(this.newOffset(), src, dst));
|
|
this.write(lineNumber, (byte) -1, (byte) -1, (byte) -1, (byte) -1);
|
|
}
|
|
|
|
private
|
|
class OffsetBranch extends Relocatable {
|
|
|
|
public
|
|
OffsetBranch(Offset where, Offset source, Offset destination) {
|
|
this.where = where;
|
|
this.source = source;
|
|
this.destination = destination;
|
|
}
|
|
|
|
@Override public boolean
|
|
relocate() {
|
|
if (this.source.offset == Offset.UNSET || this.destination.offset == Offset.UNSET) {
|
|
throw new JaninoRuntimeException("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);
|
|
return true;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Allocate an {@link Inserter}, set it to the current offset, and
|
|
* insert 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; }
|
|
|
|
/** Remember the current {@link Inserter}, then replace it with the new one. */
|
|
public void
|
|
pushInserter(Inserter ins) {
|
|
if (ins.nextInserter != null) throw new JaninoRuntimeException("An Inserter can only be pushed once at a time");
|
|
ins.nextInserter = this.currentInserter;
|
|
this.currentInserter = ins;
|
|
}
|
|
|
|
/**
|
|
* Replace 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 JaninoRuntimeException("Code inserter stack underflow");
|
|
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 succeding offsets. */
|
|
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;
|
|
|
|
/**
|
|
* Sets this "Offset" to the offset of the current inserter; inserts this "Offset" before the current inserter.
|
|
*/
|
|
public void
|
|
set() {
|
|
if (this.offset != Offset.UNSET) throw new JaninoRuntimeException("Cannot \"set()\" Offset more than once");
|
|
|
|
this.offset = CodeContext.this.currentInserter.offset;
|
|
|
|
this.prev = CodeContext.this.currentInserter.prev;
|
|
this.next = CodeContext.this.currentInserter;
|
|
this.prev.next = this;
|
|
this.next.prev = this;
|
|
}
|
|
|
|
/** @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; }
|
|
}
|
|
|
|
/**
|
|
* Add another entry to the "exception_table" of this code attribute (see JVMS 4.7.3).
|
|
*
|
|
* @param catchTypeFd null == "finally" clause
|
|
*/
|
|
public void
|
|
addExceptionTableEntry(Offset startPc, Offset endPc, Offset handlerPc, 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 {
|
|
private Inserter nextInserter; // null == not in "currentInserter" stack
|
|
}
|
|
|
|
/** An {@link Offset} who#s sole purpose is to later create a 'LneNumberTable' attribute. */
|
|
public
|
|
class LineNumberOffset extends Offset {
|
|
private final int lineNumber;
|
|
|
|
public
|
|
LineNumberOffset(int offset, int lineNumber) {
|
|
this.lineNumber = lineNumber;
|
|
this.offset = offset;
|
|
}
|
|
}
|
|
|
|
private abstract
|
|
class Relocatable {
|
|
|
|
/**
|
|
* Relocate this object.
|
|
* @return true if the relocation succeeded in place
|
|
* false if the relocation grew the number of bytes required
|
|
*/
|
|
public abstract boolean 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.
|
|
* <p>
|
|
* 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<Java.LocalVariableSlot>
|
|
getAllLocalVars() { return this.allLocalVars; }
|
|
|
|
/**
|
|
* Removes all code between {@code from} and {@code 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<Offset> invalidOffsets = new HashSet<Offset>();
|
|
{
|
|
Offset o = from.next;
|
|
|
|
for (; o != to;) {
|
|
if (o == null) {
|
|
System.currentTimeMillis();
|
|
}
|
|
invalidOffsets.add(o);
|
|
|
|
// Invalidate the offset for fast failure.
|
|
o.offset = -77;
|
|
o.prev = null;
|
|
o = o.next;
|
|
o.prev.next = null;
|
|
}
|
|
|
|
for (;; o = o.next) {
|
|
o.offset -= size;
|
|
if (o == this.end) break;
|
|
}
|
|
}
|
|
|
|
// Invalidate all relocatables which originate or target a removed offset.
|
|
for (Iterator<Relocatable> 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<ExceptionTableEntry> 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);
|
|
}
|
|
}
|
|
|
|
from.next = to;
|
|
to.prev = from;
|
|
}
|
|
}
|