/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2014 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; import proguard.util.ArrayUtil; import java.util.Arrays; /** * 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 = false; //*/ private static final int MAXIMUM_LEVELS = 32; private static final int INVALID = -1; private final boolean allowExternalBranchTargets; private final boolean allowExternalExceptionHandlers; private final boolean shrinkInstructions; 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 branch * targets or exception handlers and that automatically shrinks * instructions. */ public CodeAttributeComposer() { this(false, false, true); } /** * Creates a new CodeAttributeComposer. * @param allowExternalBranchTargets specifies whether branch targets * can lie outside the code fragment * of the branch instructions. * @param allowExternalExceptionHandlers specifies whether exception * handlers can lie outside the code * fragment in which exceptions are * defined. * @param shrinkInstructions specifies whether instructions * should automatically be shrunk * before being written. */ public CodeAttributeComposer(boolean allowExternalBranchTargets, boolean allowExternalExceptionHandlers, boolean shrinkInstructions) { this.allowExternalBranchTargets = allowExternalBranchTargets; this.allowExternalExceptionHandlers = allowExternalExceptionHandlers; this.shrinkInstructions = shrinkInstructions; } /** * Starts a new code definition. */ public void reset() { maximumCodeLength = 0; codeLength = 0; exceptionTableLength = 0; level = -1; // Make sure the instruction writer has at least the same buffer size // as the local arrays. instructionWriter.reset(code.length); } /** * 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 (more * precisely, the maximum old instruction * offset or label that is specified, plus * one). */ public void beginCodeFragment(int maximumCodeFragmentLength) { level++; if (level >= MAXIMUM_LEVELS) { throw new IllegalArgumentException("Maximum number of code fragment levels exceeded ["+level+"]"); } // Make sure there is sufficient space for adding the code fragment. // It's only a rough initial estimate for the code length, not even // necessarily a length expressed in bytes. 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. * Branch instructions must fit, for instance by enabling automatic * shrinking of instructions. * @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 (shrinkInstructions) { instruction = instruction.shrink(); } if (DEBUG) { println("["+codeLength+"] <- ", instruction.toString(oldInstructionOffset)); } // Make sure the code and offset arrays are large enough. int newCodeLength = codeLength + instruction.length(codeLength); ensureCodeLength(newCodeLength); // Remember the old offset of the appended instruction. oldInstructionOffsets[codeLength] = oldInstructionOffset; // Fill out the new offset of the appended instruction. instructionOffsetMap[level][oldInstructionOffset] = codeLength; // Write the instruction. The instruction writer may widen it later on, // if necessary. instruction.accept(null, null, new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null), codeLength, instructionWriter); //instruction.write(code, 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)"); } // Make sure the code and offset arrays are large enough. ensureCodeLength(codeLength + 1); // Remember the old offset of the following instruction. oldInstructionOffsets[codeLength] = oldInstructionOffset; // Fill out the new offset of the following instruction. instructionOffsetMap[level][oldInstructionOffset] = codeLength; } /** * Appends the given instruction without defined offsets. * @param instructions the instructions to be appended. */ public void appendInstructions(Instruction[] instructions) { for (int index = 0; index < instructions.length; index++) { appendInstruction(instructions[index]); } } /** * Appends the given instruction without a defined offset. * Branch instructions should have a label, to allow computing the * new relative offset. * Branch instructions must fit, for instance by enabling automatic * shrinking of instructions. * @param instruction the instruction to be appended. */ public void appendInstruction(Instruction instruction) { if (shrinkInstructions) { instruction = instruction.shrink(); } if (DEBUG) { println("["+codeLength+"] <- ", instruction.toString()); } // Make sure the code array is large enough. int newCodeLength = codeLength + instruction.length(codeLength); ensureCodeLength(newCodeLength); // Write the instruction. The instruction writer may widen it later on, // if necessary. instruction.accept(null, null, new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null), codeLength, instructionWriter); //instruction.write(code, codeLength); // Continue appending at the next instruction offset. codeLength = newCodeLength; } /** * 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; } // Add the exception. exceptionTable = (ExceptionInfo[])ArrayUtil.add(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. The instruction writer may still // widen it later on, if necessary. instruction.accept(null, null, new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null), instructionOffset, instructionWriter); //instruction.write(code, codeLength); } // 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 (remappableExceptionHandler(handlerPC)) { exceptionInfo.u2handlerPC = newInstructionOffset(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 (done before). //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) { try { // Adjust the branch offset. branchInstruction.branchOffset = newBranchOffset(offset, branchInstruction.branchOffset); // Don't remap this instruction again. oldInstructionOffsets[offset] = -1; } catch (IllegalArgumentException e) { if (level == 0 || !allowExternalBranchTargets) { throw e; } } } public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) { try { // TODO: We're assuming we can adjust no offsets or all offsets at once. // Adjust the default jump offset. switchInstruction.defaultOffset = newBranchOffset(offset, switchInstruction.defaultOffset); // Adjust the jump offsets. updateJumpOffsets(offset, switchInstruction.jumpOffsets); // Don't remap this instruction again. oldInstructionOffsets[offset] = -1; } catch (IllegalArgumentException e) { if (level == 0 || !allowExternalBranchTargets) { throw e; } } } // 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 = newInstructionOffset(exceptionInfo.u2startPC); exceptionInfo.u2endPC = newInstructionOffset(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 || remappableExceptionHandler(handlerPC) ? newInstructionOffset(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 = newInstructionOffset(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 = newInstructionOffset(uninitializedType.u2newInstructionOffset); } // Implementations for LineNumberInfoVisitor. public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) { // Remap the code offset. lineNumberInfo.u2startPC = newInstructionOffset(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 = newInstructionOffset(localVariableInfo.u2startPC); int endPC = newInstructionOffset(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 = newInstructionOffset(localVariableTypeInfo.u2startPC); int endPC = newInstructionOffset(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; code = ArrayUtil.extendArray(code, newCodeLength); oldInstructionOffsets = ArrayUtil.extendArray(oldInstructionOffsets, newCodeLength); instructionWriter.extend(newCodeLength); } } /** * Adjusts the given jump offsets for the instruction at the given offset. */ private void updateJumpOffsets(int offset, int[] jumpOffsets) { for (int index = 0; index < jumpOffsets.length; index++) { jumpOffsets[index] = newBranchOffset(offset, jumpOffsets[index]); } } /** * Computes the new branch offset for the instruction at the given new offset * with the given old branch offset. */ private int newBranchOffset(int newInstructionOffset, int oldBranchOffset) { if (newInstructionOffset < 0 || newInstructionOffset > codeLength) { throw new IllegalArgumentException("Invalid instruction offset ["+newInstructionOffset +"] in code with length ["+codeLength+"]"); } int oldInstructionOffset = oldInstructionOffsets[newInstructionOffset]; // For ordinary branch instructions, we can compute the offset // relative to the instruction itself. return newInstructionOffset(oldInstructionOffset + oldBranchOffset) - newInstructionOffset; } /** * Computes the new instruction offset for the instruction at the given old * offset. */ private int newInstructionOffset(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 exception handler can be remapped in the * current code fragment. */ private boolean remappableExceptionHandler(int oldInstructionOffset) { // Can we index in the array? if (oldInstructionOffset > codeFragmentLengths[level]) { return false; } // Do we have a valid new instruction offset, but not yet right after // the code? That offset is only labeled for mapping try blocks, not // for mapping handlers. int newInstructionOffset = instructionOffsetMap[level][oldInstructionOffset]; return newInstructionOffset > INVALID && newInstructionOffset < codeLength; } /** * 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. Arrays.fill(exceptionInfos, newIndex, exceptionInfoCount, 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. Arrays.fill(lineNumberInfos, newIndex, lineNumberInfoCount, 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. Arrays.fill(localVariableInfos, newIndex, localVariableInfoCount, 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. Arrays.fill(localVariableTypeInfos, newIndex, localVariableTypeInfoCount, 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(); } }