blob: e78320341f60cb7966f70caea763e64605bf1178 [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package proguard.classfile.editor;
import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.preverification.*;
import proguard.classfile.attribute.preverification.visitor.*;
import proguard.classfile.attribute.visitor.*;
import proguard.classfile.instruction.*;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.util.SimplifiedVisitor;
/**
* This AttributeVisitor accumulates instructions and exceptions, and then
* copies them into code attributes that it visits.
*
* @author Eric Lafortune
*/
public class CodeAttributeComposer
extends SimplifiedVisitor
implements AttributeVisitor,
InstructionVisitor,
ExceptionInfoVisitor,
StackMapFrameVisitor,
VerificationTypeVisitor,
LineNumberInfoVisitor,
LocalVariableInfoVisitor,
LocalVariableTypeInfoVisitor
{
//*
private static final boolean DEBUG = false;
/*/
public static boolean DEBUG = true;
//*/
private static final int MAXIMUM_LEVELS = 32;
private static final int INVALID = -1;
private boolean allowExternalExceptionHandlers;
private int maximumCodeLength;
private int codeLength;
private int exceptionTableLength;
private int level = -1;
private byte[] code = new byte[ClassConstants.TYPICAL_CODE_LENGTH];
private int[] oldInstructionOffsets = new int[ClassConstants.TYPICAL_CODE_LENGTH];
private final int[] codeFragmentOffsets = new int[MAXIMUM_LEVELS];
private final int[] codeFragmentLengths = new int[MAXIMUM_LEVELS];
private final int[][] instructionOffsetMap = new int[MAXIMUM_LEVELS][ClassConstants.TYPICAL_CODE_LENGTH + 1];
private ExceptionInfo[] exceptionTable = new ExceptionInfo[ClassConstants.TYPICAL_EXCEPTION_TABLE_LENGTH];
private int expectedStackMapFrameOffset;
private final StackSizeUpdater stackSizeUpdater = new StackSizeUpdater();
private final VariableSizeUpdater variableSizeUpdater = new VariableSizeUpdater();
// private final InstructionWriter instructionWriter = new InstructionWriter();
/**
* Creates a new CodeAttributeComposer that doesn't allow external exception
* handlers.
*/
public CodeAttributeComposer()
{
this(false);
}
/**
* Creates a new CodeAttributeComposer that optionally allows external
* exception handlers.
*/
public CodeAttributeComposer(boolean allowExternalExceptionHandlers)
{
this.allowExternalExceptionHandlers = allowExternalExceptionHandlers;
}
/**
* Starts a new code definition.
*/
public void reset()
{
maximumCodeLength = 0;
codeLength = 0;
exceptionTableLength = 0;
level = -1;
}
/**
* Starts a new code fragment. Branch instructions that are added are
* assumed to be relative within such code fragments.
* @param maximumCodeFragmentLength the maximum length of the code that will
* be added as part of this fragment.
*/
public void beginCodeFragment(int maximumCodeFragmentLength)
{
level++;
if (level >= MAXIMUM_LEVELS)
{
throw new IllegalArgumentException("Maximum number of code fragment levels exceeded ["+level+"]");
}
// // TODO: Figure out some length.
// if (level == 0)
// {
// // Prepare for possible widening of instructions.
// instructionWriter.reset(2 * maximumCodeFragmentLength);
// }
// Make sure there is sufficient space for adding the code fragment.
maximumCodeLength += maximumCodeFragmentLength;
ensureCodeLength(maximumCodeLength);
// Try to reuse the previous array for this code fragment.
if (instructionOffsetMap[level].length <= maximumCodeFragmentLength)
{
instructionOffsetMap[level] = new int[maximumCodeFragmentLength + 1];
}
// Initialize the offset map.
for (int index = 0; index <= maximumCodeFragmentLength; index++)
{
instructionOffsetMap[level][index] = INVALID;
}
// Remember the location of the code fragment.
codeFragmentOffsets[level] = codeLength;
codeFragmentLengths[level] = maximumCodeFragmentLength;
}
/**
* Appends the given instruction with the given old offset.
* @param oldInstructionOffset the old offset of the instruction, to which
* branches and other references in the current
* code fragment are pointing.
* @param instruction the instruction to be appended.
*/
public void appendInstruction(int oldInstructionOffset,
Instruction instruction)
{
if (DEBUG)
{
println("["+codeLength+"] <- ", instruction.toString(oldInstructionOffset));
}
// Make sure the code array is large enough.
int newCodeLength = codeLength + instruction.length(codeLength);
ensureCodeLength(newCodeLength);
// Remember the old offset of the appended instruction.
oldInstructionOffsets[codeLength] = oldInstructionOffset;
// Write the instruction.
// instruction.accept(null,
// null,
// new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null),
// codeLength,
// instructionWriter);
instruction.write(code, codeLength);
// Fill out the new offset of the appended instruction.
instructionOffsetMap[level][oldInstructionOffset] = codeLength;
// Continue appending at the next instruction offset.
codeLength = newCodeLength;
}
/**
* Appends the given label with the given old offset.
* @param oldInstructionOffset the old offset of the label, to which
* branches and other references in the current
* code fragment are pointing.
*/
public void appendLabel(int oldInstructionOffset)
{
if (DEBUG)
{
println("["+codeLength+"] <- ", "[" + oldInstructionOffset + "] (label)");
}
// Fill out the new offset of the appended instruction.
instructionOffsetMap[level][oldInstructionOffset] = codeLength;
}
/**
* Appends the given exception to the exception table.
* @param exceptionInfo the exception to be appended.
*/
public void appendException(ExceptionInfo exceptionInfo)
{
if (DEBUG)
{
print(" ", "Exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+": "+exceptionInfo.u2handlerPC+"]");
}
// Remap the exception right away.
visitExceptionInfo(null, null, null, exceptionInfo);
if (DEBUG)
{
System.out.println(" -> ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+": "+exceptionInfo.u2handlerPC+"]");
}
// Don't add the exception if its instruction range is empty.
if (exceptionInfo.u2startPC == exceptionInfo.u2endPC)
{
if (DEBUG)
{
println(" ", " (not added because of empty instruction range)");
}
return;
}
// Make sure there is sufficient space in the exception table.
if (exceptionTable.length <= exceptionTableLength)
{
ExceptionInfo[] newExceptionTable = new ExceptionInfo[exceptionTableLength+1];
System.arraycopy(exceptionTable, 0, newExceptionTable, 0, exceptionTableLength);
exceptionTable = newExceptionTable;
}
// Add the exception.
exceptionTable[exceptionTableLength++] = exceptionInfo;
}
/**
* Wraps up the current code fragment, continuing with the previous one on
* the stack.
*/
public void endCodeFragment()
{
if (level < 0)
{
throw new IllegalArgumentException("Code fragment not begun ["+level+"]");
}
// Remap the instructions of the code fragment.
int instructionOffset = codeFragmentOffsets[level];
while (instructionOffset < codeLength)
{
// Get the next instruction.
Instruction instruction = InstructionFactory.create(code, instructionOffset);
// Does this instruction still have to be remapped?
if (oldInstructionOffsets[instructionOffset] >= 0)
{
// Adapt the instruction for its new offset.
instruction.accept(null, null, null, instructionOffset, this);
// Write the instruction back.
// instruction.accept(null,
// null,
// new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null),
// instructionOffset,
// instructionWriter);
instruction.write(code, instructionOffset);
// Don't remap this instruction again.
oldInstructionOffsets[instructionOffset] = -1;
}
// Continue remapping at the next instruction offset.
instructionOffset += instruction.length(instructionOffset);
}
// Correct the estimated maximum code length, now that we know the
// actual length of this code fragment.
maximumCodeLength += codeLength - codeFragmentOffsets[level] -
codeFragmentLengths[level];
// Try to remap the exception handlers that couldn't be remapped before.
if (allowExternalExceptionHandlers)
{
for (int index = 0; index < exceptionTableLength; index++)
{
ExceptionInfo exceptionInfo = exceptionTable[index];
// Unmapped exception handlers are still negated.
int handlerPC = -exceptionInfo.u2handlerPC;
if (handlerPC > 0)
{
if (remappableInstructionOffset(handlerPC))
{
exceptionInfo.u2handlerPC = remapInstructionOffset(handlerPC);
}
else if (level == 0)
{
throw new IllegalStateException("Couldn't remap exception handler offset ["+handlerPC+"]");
}
}
}
}
level--;
}
// Implementations for AttributeVisitor.
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
if (DEBUG)
{
System.out.println("CodeAttributeComposer: putting results in ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]");
}
if (level != -1)
{
throw new IllegalArgumentException("Code fragment not ended ["+level+"]");
}
level++;
// Make sure the code attribute has sufficient space for the composed
// code.
if (codeAttribute.u4codeLength < codeLength)
{
codeAttribute.code = new byte[codeLength];
}
// Copy the composed code over into the code attribute.
codeAttribute.u4codeLength = codeLength;
System.arraycopy(code, 0, codeAttribute.code, 0, codeLength);
// Remove exceptions with empty code blocks (done before).
//exceptionTableLength =
// removeEmptyExceptions(exceptionTable, exceptionTableLength);
// Make sure the exception table has sufficient space for the composed
// exceptions.
if (codeAttribute.exceptionTable.length < exceptionTableLength)
{
codeAttribute.exceptionTable = new ExceptionInfo[exceptionTableLength];
}
// Copy the exception table.
codeAttribute.u2exceptionTableLength = exceptionTableLength;
System.arraycopy(exceptionTable, 0, codeAttribute.exceptionTable, 0, exceptionTableLength);
// Update the maximum stack size and local variable frame size.
stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
// Remap the line number table and the local variable table.
codeAttribute.attributesAccept(clazz, method, this);
// Remap the exception table.
//codeAttribute.exceptionsAccept(clazz, method, this);
// Remove exceptions with empty code blocks (done before).
//codeAttribute.u2exceptionTableLength =
// removeEmptyExceptions(codeAttribute.exceptionTable,
// codeAttribute.u2exceptionTableLength);
// // Make sure instructions are widened if necessary.
// instructionWriter.visitCodeAttribute(clazz, method, codeAttribute);
level--;
}
public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
{
// Remap all stack map entries.
expectedStackMapFrameOffset = -1;
stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
}
public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
{
// Remap all stack map table entries.
expectedStackMapFrameOffset = 0;
stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
}
public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
{
// Remap all line number table entries.
lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
// Remove line numbers with empty code blocks.
lineNumberTableAttribute.u2lineNumberTableLength =
removeEmptyLineNumbers(lineNumberTableAttribute.lineNumberTable,
lineNumberTableAttribute.u2lineNumberTableLength,
codeAttribute.u4codeLength);
}
public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
{
// Remap all local variable table entries.
localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
// Remove local variables with empty code blocks.
localVariableTableAttribute.u2localVariableTableLength =
removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable,
localVariableTableAttribute.u2localVariableTableLength,
codeAttribute.u2maxLocals);
}
public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
{
// Remap all local variable table entries.
localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
// Remove local variables with empty code blocks.
localVariableTypeTableAttribute.u2localVariableTypeTableLength =
removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable,
localVariableTypeTableAttribute.u2localVariableTypeTableLength,
codeAttribute.u2maxLocals);
}
// Implementations for InstructionVisitor.
public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
{
// Adjust the branch offset.
branchInstruction.branchOffset = remapBranchOffset(offset,
branchInstruction.branchOffset);
}
public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
{
// Adjust the default jump offset.
switchInstruction.defaultOffset = remapBranchOffset(offset,
switchInstruction.defaultOffset);
// Adjust the jump offsets.
remapJumpOffsets(offset,
switchInstruction.jumpOffsets);
}
// Implementations for ExceptionInfoVisitor.
public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
{
// Remap the code offsets. Note that the instruction offset map also has
// an entry for the first offset after the code, for u2endPC.
exceptionInfo.u2startPC = remapInstructionOffset(exceptionInfo.u2startPC);
exceptionInfo.u2endPC = remapInstructionOffset(exceptionInfo.u2endPC);
// See if we can remap the handler right away. Unmapped exception
// handlers are negated, in order to mark them as external.
int handlerPC = exceptionInfo.u2handlerPC;
exceptionInfo.u2handlerPC =
!allowExternalExceptionHandlers ||
remappableInstructionOffset(handlerPC) ?
remapInstructionOffset(handlerPC) :
-handlerPC;
}
// Implementations for StackMapFrameVisitor.
public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame)
{
// Remap the stack map frame offset.
int stackMapFrameOffset = remapInstructionOffset(offset);
int offsetDelta = stackMapFrameOffset;
// Compute the offset delta if the frame is part of a stack map frame
// table (for JDK 6.0) instead of a stack map (for Java Micro Edition).
if (expectedStackMapFrameOffset >= 0)
{
offsetDelta -= expectedStackMapFrameOffset;
expectedStackMapFrameOffset = stackMapFrameOffset + 1;
}
stackMapFrame.u2offsetDelta = offsetDelta;
}
public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
{
// Remap the stack map frame offset.
visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame);
// Remap the verification type offset.
sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
}
public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
{
// Remap the stack map frame offset.
visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame);
// Remap the verification type offsets.
moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
}
public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
{
// Remap the stack map frame offset.
visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame);
// Remap the verification type offsets.
fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
}
// Implementations for VerificationTypeVisitor.
public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {}
public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)
{
// Remap the offset of the 'new' instruction.
uninitializedType.u2newInstructionOffset = remapInstructionOffset(uninitializedType.u2newInstructionOffset);
}
// Implementations for LineNumberInfoVisitor.
public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
{
// Remap the code offset.
lineNumberInfo.u2startPC = remapInstructionOffset(lineNumberInfo.u2startPC);
}
// Implementations for LocalVariableInfoVisitor.
public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
{
// Remap the code offset and length.
// TODO: The local variable frame might not be strictly preserved.
int startPC = remapInstructionOffset(localVariableInfo.u2startPC);
int endPC = remapInstructionOffset(localVariableInfo.u2startPC + localVariableInfo.u2length);
localVariableInfo.u2startPC = startPC;
localVariableInfo.u2length = endPC - startPC;
}
// Implementations for LocalVariableTypeInfoVisitor.
public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
{
// Remap the code offset and length.
// TODO: The local variable frame might not be strictly preserved.
int startPC = remapInstructionOffset(localVariableTypeInfo.u2startPC);
int endPC = remapInstructionOffset(localVariableTypeInfo.u2startPC + localVariableTypeInfo.u2length);
localVariableTypeInfo.u2startPC = startPC;
localVariableTypeInfo.u2length = endPC - startPC;
}
// Small utility methods.
/**
* Make sure the code arrays have at least the given size.
*/
private void ensureCodeLength(int newCodeLength)
{
if (code.length < newCodeLength)
{
// Add 20% to avoid extending the arrays too often.
newCodeLength = newCodeLength * 6 / 5;
byte[] newCode = new byte[newCodeLength];
System.arraycopy(code, 0, newCode, 0, codeLength);
code = newCode;
int[] newOldInstructionOffsets = new int[newCodeLength];
System.arraycopy(oldInstructionOffsets, 0, newOldInstructionOffsets, 0, codeLength);
oldInstructionOffsets = newOldInstructionOffsets;
}
}
/**
* Adjusts the given jump offsets for the instruction at the given offset.
*/
private void remapJumpOffsets(int offset, int[] jumpOffsets)
{
for (int index = 0; index < jumpOffsets.length; index++)
{
jumpOffsets[index] = remapBranchOffset(offset, jumpOffsets[index]);
}
}
/**
* Computes the new branch offset for the instruction at the given new offset
* with the given old branch offset.
*/
private int remapBranchOffset(int newInstructionOffset, int branchOffset)
{
if (newInstructionOffset < 0 ||
newInstructionOffset > codeLength)
{
throw new IllegalArgumentException("Invalid instruction offset ["+newInstructionOffset +"] in code with length ["+codeLength+"]");
}
int oldInstructionOffset = oldInstructionOffsets[newInstructionOffset];
return remapInstructionOffset(oldInstructionOffset + branchOffset) -
remapInstructionOffset(oldInstructionOffset);
}
/**
* Computes the new instruction offset for the instruction at the given old
* offset.
*/
private int remapInstructionOffset(int oldInstructionOffset)
{
if (oldInstructionOffset < 0 ||
oldInstructionOffset > codeFragmentLengths[level])
{
throw new IllegalArgumentException("Instruction offset ["+oldInstructionOffset +"] out of range in code fragment with length ["+codeFragmentLengths[level]+"] at level "+level);
}
int newInstructionOffset = instructionOffsetMap[level][oldInstructionOffset];
if (newInstructionOffset == INVALID)
{
throw new IllegalArgumentException("Invalid instruction offset ["+oldInstructionOffset +"] in code fragment at level "+level);
}
return newInstructionOffset;
}
/**
* Returns whether the given old instruction offset can be remapped at the
*/
private boolean remappableInstructionOffset(int oldInstructionOffset)
{
return
oldInstructionOffset <= codeFragmentLengths[level] &&
instructionOffsetMap[level][oldInstructionOffset] > INVALID;
}
/**
* Returns the given list of exceptions, without the ones that have empty
* code blocks.
*/
private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos,
int exceptionInfoCount)
{
// Overwrite all empty exceptions.
int newIndex = 0;
for (int index = 0; index < exceptionInfoCount; index++)
{
ExceptionInfo exceptionInfo = exceptionInfos[index];
if (exceptionInfo.u2startPC < exceptionInfo.u2endPC)
{
exceptionInfos[newIndex++] = exceptionInfo;
}
}
// Clear the unused array entries.
for (int index = newIndex; index < exceptionInfoCount; index++)
{
exceptionInfos[index] = null;
}
return newIndex;
}
/**
* Returns the given list of line numbers, without the ones that have empty
* code blocks or that exceed the code size.
*/
private int removeEmptyLineNumbers(LineNumberInfo[] lineNumberInfos,
int lineNumberInfoCount,
int codeLength)
{
// Overwrite all empty line number entries.
int newIndex = 0;
for (int index = 0; index < lineNumberInfoCount; index++)
{
LineNumberInfo lineNumberInfo = lineNumberInfos[index];
int startPC = lineNumberInfo.u2startPC;
if (startPC < codeLength &&
(index == 0 || startPC > lineNumberInfos[index-1].u2startPC))
{
lineNumberInfos[newIndex++] = lineNumberInfo;
}
}
// Clear the unused array entries.
for (int index = newIndex; index < lineNumberInfoCount; index++)
{
lineNumberInfos[index] = null;
}
return newIndex;
}
/**
* Returns the given list of local variables, without the ones that have empty
* code blocks or that exceed the actual number of local variables.
*/
private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos,
int localVariableInfoCount,
int maxLocals)
{
// Overwrite all empty local variable entries.
int newIndex = 0;
for (int index = 0; index < localVariableInfoCount; index++)
{
LocalVariableInfo localVariableInfo = localVariableInfos[index];
if (localVariableInfo.u2length > 0 &&
localVariableInfo.u2index < maxLocals)
{
localVariableInfos[newIndex++] = localVariableInfo;
}
}
// Clear the unused array entries.
for (int index = newIndex; index < localVariableInfoCount; index++)
{
localVariableInfos[index] = null;
}
return newIndex;
}
/**
* Returns the given list of local variable types, without the ones that
* have empty code blocks or that exceed the actual number of local variables.
*/
private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos,
int localVariableTypeInfoCount,
int maxLocals)
{
// Overwrite all empty local variable type entries.
int newIndex = 0;
for (int index = 0; index < localVariableTypeInfoCount; index++)
{
LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeInfos[index];
if (localVariableTypeInfo.u2length > 0 &&
localVariableTypeInfo.u2index < maxLocals)
{
localVariableTypeInfos[newIndex++] = localVariableTypeInfo;
}
}
// Clear the unused array entries.
for (int index = newIndex; index < localVariableTypeInfoCount; index++)
{
localVariableTypeInfos[index] = null;
}
return newIndex;
}
private void println(String string1, String string2)
{
print(string1, string2);
System.out.println();
}
private void print(String string1, String string2)
{
System.out.print(string1);
for (int index = 0; index < level; index++)
{
System.out.print(" ");
}
System.out.print(string2);
}
public static void main(String[] args)
{
CodeAttributeComposer composer = new CodeAttributeComposer();
composer.beginCodeFragment(4);
composer.appendInstruction(0, new SimpleInstruction(InstructionConstants.OP_ICONST_0));
composer.appendInstruction(1, new VariableInstruction(InstructionConstants.OP_ISTORE, 0));
composer.appendInstruction(2, new BranchInstruction(InstructionConstants.OP_GOTO, 1));
composer.beginCodeFragment(4);
composer.appendInstruction(0, new VariableInstruction(InstructionConstants.OP_IINC, 0, 1));
composer.appendInstruction(1, new VariableInstruction(InstructionConstants.OP_ILOAD, 0));
composer.appendInstruction(2, new SimpleInstruction(InstructionConstants.OP_ICONST_5));
composer.appendInstruction(3, new BranchInstruction(InstructionConstants.OP_IFICMPLT, -3));
composer.endCodeFragment();
composer.appendInstruction(3, new SimpleInstruction(InstructionConstants.OP_RETURN));
composer.endCodeFragment();
}
}