diff options
Diffstat (limited to 'src/proguard/classfile/editor/CodeAttributeEditor.java')
-rw-r--r-- | src/proguard/classfile/editor/CodeAttributeEditor.java | 1163 |
1 files changed, 1163 insertions, 0 deletions
diff --git a/src/proguard/classfile/editor/CodeAttributeEditor.java b/src/proguard/classfile/editor/CodeAttributeEditor.java new file mode 100644 index 0000000..9658c98 --- /dev/null +++ b/src/proguard/classfile/editor/CodeAttributeEditor.java @@ -0,0 +1,1163 @@ +/* + * 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; +import proguard.classfile.visitor.ClassPrinter; + +/** + * This AttributeVisitor accumulates specified changes to code, and then applies + * these accumulated changes to the code attributes that it visits. + * + * @author Eric Lafortune + */ +public class CodeAttributeEditor +extends SimplifiedVisitor +implements AttributeVisitor, + InstructionVisitor, + ExceptionInfoVisitor, + StackMapFrameVisitor, + VerificationTypeVisitor, + LineNumberInfoVisitor, + LocalVariableInfoVisitor, + LocalVariableTypeInfoVisitor +{ + //* + private static final boolean DEBUG = false; + /*/ + private static boolean DEBUG = true; + //*/ + + private boolean updateFrameSizes; + + private int codeLength; + private boolean modified; + private boolean simple; + + /*private*/public Instruction[] preInsertions = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; + /*private*/public Instruction[] replacements = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; + /*private*/public Instruction[] postInsertions = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; + /*private*/public boolean[] deleted = new boolean[ClassConstants.TYPICAL_CODE_LENGTH]; + + private int[] instructionOffsetMap = new int[ClassConstants.TYPICAL_CODE_LENGTH]; + private int newOffset; + private boolean lengthIncreased; + + private int expectedStackMapFrameOffset; + + private final StackSizeUpdater stackSizeUpdater = new StackSizeUpdater(); + private final VariableSizeUpdater variableSizeUpdater = new VariableSizeUpdater(); + private final InstructionWriter instructionWriter = new InstructionWriter(); + + + public CodeAttributeEditor() + { + this(true); + } + + + public CodeAttributeEditor(boolean updateFrameSizes) + { + this.updateFrameSizes = updateFrameSizes; + } + + + /** + * Resets the accumulated code changes. + * @param codeLength the length of the code that will be edited next. + */ + public void reset(int codeLength) + { + this.codeLength = codeLength; + + // Try to reuse the previous arrays. + if (preInsertions.length < codeLength) + { + preInsertions = new Instruction[codeLength]; + replacements = new Instruction[codeLength]; + postInsertions = new Instruction[codeLength]; + deleted = new boolean[codeLength]; + } + else + { + for (int index = 0; index < codeLength; index++) + { + preInsertions[index] = null; + replacements[index] = null; + postInsertions[index] = null; + deleted[index] = false; + } + } + + modified = false; + simple = true; + + } + + + /** + * Remembers to place the given instruction right before the instruction + * at the given offset. + * @param instructionOffset the offset of the instruction. + * @param instruction the new instruction. + */ + public void insertBeforeInstruction(int instructionOffset, Instruction instruction) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + preInsertions[instructionOffset] = instruction; + + modified = true; + simple = false; + + } + + + /** + * Remembers to place the given instructions right before the instruction + * at the given offset. + * @param instructionOffset the offset of the instruction. + * @param instructions the new instructions. + */ + public void insertBeforeInstruction(int instructionOffset, Instruction[] instructions) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + preInsertions[instructionOffset] = new CompositeInstruction(instructions); + + modified = true; + simple = false; + + } + + + /** + * Remembers to replace the instruction at the given offset by the given + * instruction. + * @param instructionOffset the offset of the instruction to be replaced. + * @param instruction the new instruction. + */ + public void replaceInstruction(int instructionOffset, Instruction instruction) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + replacements[instructionOffset] = instruction; + + modified = true; + } + + + /** + * Remembers to replace the instruction at the given offset by the given + * instructions. + * @param instructionOffset the offset of the instruction to be replaced. + * @param instructions the new instructions. + */ + public void replaceInstruction(int instructionOffset, Instruction[] instructions) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + replacements[instructionOffset] = new CompositeInstruction(instructions); + + modified = true; + } + + + /** + * Remembers to place the given instruction right after the instruction + * at the given offset. + * @param instructionOffset the offset of the instruction. + * @param instruction the new instruction. + */ + public void insertAfterInstruction(int instructionOffset, Instruction instruction) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + postInsertions[instructionOffset] = instruction; + + modified = true; + simple = false; + } + + + /** + * Remembers to place the given instructions right after the instruction + * at the given offset. + * @param instructionOffset the offset of the instruction. + * @param instructions the new instructions. + */ + public void insertAfterInstruction(int instructionOffset, Instruction[] instructions) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + postInsertions[instructionOffset] = new CompositeInstruction(instructions); + + modified = true; + simple = false; + } + + + /** + * Remembers to delete the instruction at the given offset. + * @param instructionOffset the offset of the instruction to be deleted. + */ + public void deleteInstruction(int instructionOffset) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + deleted[instructionOffset] = true; + + modified = true; + simple = false; + } + + + /** + * Remembers not to delete the instruction at the given offset. + * @param instructionOffset the offset of the instruction not to be deleted. + */ + public void undeleteInstruction(int instructionOffset) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + deleted[instructionOffset] = false; + } + + + /** + * Returns whether the instruction at the given offset has been modified + * in any way. + */ + public boolean isModified(int instructionOffset) + { + return preInsertions[instructionOffset] != null || + replacements[instructionOffset] != null || + postInsertions[instructionOffset] != null || + deleted[instructionOffset]; + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { +// DEBUG = +// clazz.getName().equals("abc/Def") && +// method.getName(clazz).equals("abc"); + + // TODO: Remove this when the code has stabilized. + // Catch any unexpected exceptions from the actual visiting method. + try + { + // Process the code. + visitCodeAttribute0(clazz, method, codeAttribute); + } + catch (RuntimeException ex) + { + System.err.println("Unexpected error while editing code:"); + System.err.println(" Class = ["+clazz.getName()+"]"); + System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); + System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); + + throw ex; + } + } + + + public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + if (DEBUG) + { + System.out.println("CodeAttributeEditor: ["+clazz.getName()+"."+method.getName(clazz)+"]"); + } + + // Avoid doing any work if nothing is changing anyway. + if (!modified) + { + return; + } + + // Check if we can perform a faster simple replacement of instructions. + if (canPerformSimpleReplacements(codeAttribute)) + { + // Simply overwrite the instructions. + performSimpleReplacements(codeAttribute); + + // Update the maximum stack size and local variable frame size. + updateFrameSizes(clazz, method, codeAttribute); + } + else + { + // Move and remap the instructions. + codeAttribute.u4codeLength = + updateInstructions(clazz, method, codeAttribute); + + // Remap the exception table. + codeAttribute.exceptionsAccept(clazz, method, this); + + // Remove exceptions with empty code blocks. + codeAttribute.u2exceptionTableLength = + removeEmptyExceptions(codeAttribute.exceptionTable, + codeAttribute.u2exceptionTableLength); + + // Update the maximum stack size and local variable frame size. + updateFrameSizes(clazz, method, codeAttribute); + + // Remap the line number table and the local variable table. + codeAttribute.attributesAccept(clazz, method, this); + + // Make sure instructions are widened if necessary. + instructionWriter.visitCodeAttribute(clazz, method, codeAttribute); + } + } + + + private void updateFrameSizes(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + if (updateFrameSizes) + { + stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); + variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); + } + } + + + 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); + } + + + /** + * Checks if it is possible to modifies the given code without having to + * update any offsets. + * @param codeAttribute the code to be changed. + * @return the new code length. + */ + private boolean canPerformSimpleReplacements(CodeAttribute codeAttribute) + { + if (!simple) + { + return false; + } + + byte[] code = codeAttribute.code; + int codeLength = codeAttribute.u4codeLength; + + // Go over all replacement instructions. + for (int offset = 0; offset < codeLength; offset++) + { + // Check if the replacement instruction, if any, has a different + // length than the original instruction. + Instruction replacementInstruction = replacements[offset]; + if (replacementInstruction != null && + replacementInstruction.length(offset) != + InstructionFactory.create(code, offset).length(offset)) + { + return false; + } + } + + return true; + } + + + /** + * Modifies the given code without updating any offsets. + * @param codeAttribute the code to be changed. + */ + private void performSimpleReplacements(CodeAttribute codeAttribute) + { + int codeLength = codeAttribute.u4codeLength; + + // Go over all replacement instructions. + for (int offset = 0; offset < codeLength; offset++) + { + // Overwrite the original instruction with the replacement + // instruction if any. + Instruction replacementInstruction = replacements[offset]; + if (replacementInstruction != null) + { + replacementInstruction.write(codeAttribute, offset); + + if (DEBUG) + { + System.out.println(" Replaced "+replacementInstruction.toString(newOffset)); + } + } + } + } + + + /** + * Modifies the given code based on the previously specified changes. + * @param clazz the class file of the code to be changed. + * @param method the method of the code to be changed. + * @param codeAttribute the code to be changed. + * @return the new code length. + */ + private int updateInstructions(Clazz clazz, + Method method, + CodeAttribute codeAttribute) + { + byte[] oldCode = codeAttribute.code; + int oldLength = codeAttribute.u4codeLength; + + // Make sure there is a sufficiently large instruction offset map. + if (instructionOffsetMap.length < oldLength + 1) + { + instructionOffsetMap = new int[oldLength + 1]; + } + + // Fill out the instruction offset map. + int newLength = mapInstructions(oldCode, + oldLength); + + // Create a new code array if necessary. + if (lengthIncreased) + { + codeAttribute.code = new byte[newLength]; + } + + // Prepare for possible widening of instructions. + instructionWriter.reset(newLength); + + // Move the instructions into the new code array. + moveInstructions(clazz, + method, + codeAttribute, + oldCode, + oldLength); + + // We can return the new length. + return newLength; + } + + + /** + * Fills out the instruction offset map for the given code block. + * @param oldCode the instructions to be moved. + * @param oldLength the code length. + * @return the new code length. + */ + private int mapInstructions(byte[] oldCode, int oldLength) + { + // Start mapping instructions at the beginning. + newOffset = 0; + lengthIncreased = false; + + int oldOffset = 0; + do + { + // Get the next instruction. + Instruction instruction = InstructionFactory.create(oldCode, oldOffset); + + // Compute the mapping of the instruction. + mapInstruction(oldOffset, instruction); + + oldOffset += instruction.length(oldOffset); + + if (newOffset > oldOffset) + { + lengthIncreased = true; + } + } + while (oldOffset < oldLength); + + // Also add an entry for the first offset after the code. + instructionOffsetMap[oldOffset] = newOffset; + + return newOffset; + } + + + /** + * Fills out the instruction offset map for the given instruction. + * @param oldOffset the instruction's old offset. + * @param instruction the instruction to be moved. + */ + private void mapInstruction(int oldOffset, + Instruction instruction) + { + instructionOffsetMap[oldOffset] = newOffset; + + // Account for the pre-inserted instruction, if any. + Instruction preInstruction = preInsertions[oldOffset]; + if (preInstruction != null) + { + newOffset += preInstruction.length(newOffset); + } + + // Account for the replacement instruction, or for the current + // instruction, if it shouldn't be deleted. + Instruction replacementInstruction = replacements[oldOffset]; + if (replacementInstruction != null) + { + newOffset += replacementInstruction.length(newOffset); + } + else if (!deleted[oldOffset]) + { + // Note that the instruction's length may change at its new offset, + // e.g. if it is a switch instruction. + newOffset += instruction.length(newOffset); + } + + // Account for the post-inserted instruction, if any. + Instruction postInstruction = postInsertions[oldOffset]; + if (postInstruction != null) + { + newOffset += postInstruction.length(newOffset); + } + } + + + /** + * Moves the given code block to the new offsets. + * @param clazz the class file of the code to be changed. + * @param method the method of the code to be changed. + * @param codeAttribute the code to be changed. + * @param oldCode the original code to be moved. + * @param oldLength the original code length. + */ + private void moveInstructions(Clazz clazz, + Method method, + CodeAttribute codeAttribute, + byte[] oldCode, + int oldLength) + { + // Start writing instructions at the beginning. + newOffset = 0; + + int oldOffset = 0; + do + { + // Get the next instruction. + Instruction instruction = InstructionFactory.create(oldCode, oldOffset); + + // Move the instruction to its new offset. + moveInstruction(clazz, + method, + codeAttribute, + oldOffset, + instruction); + + oldOffset += instruction.length(oldOffset); + } + while (oldOffset < oldLength); + } + + + /** + * Moves the given instruction to its new offset. + * @param clazz the class file of the code to be changed. + * @param method the method of the code to be changed. + * @param codeAttribute the code to be changed. + * @param oldOffset the original instruction offset. + * @param instruction the original instruction. + */ + private void moveInstruction(Clazz clazz, + Method method, + CodeAttribute codeAttribute, + int oldOffset, + Instruction instruction) + { + // Remap and insert the pre-inserted instruction, if any. + Instruction preInstruction = preInsertions[oldOffset]; + if (preInstruction != null) + { + if (DEBUG) + { + System.out.println(" Pre-inserted "+preInstruction.toString(newOffset)); + } + + // Remap the instruction. + preInstruction.accept(clazz, method, codeAttribute, oldOffset, this); + } + + // Remap and insert the replacement instruction, or the current + // instruction, if it shouldn't be deleted. + Instruction replacementInstruction = replacements[oldOffset]; + if (replacementInstruction != null) + { + if (DEBUG) + { + System.out.println(" Replaced "+replacementInstruction.toString(newOffset)); + } + // Remap the instruction. + replacementInstruction.accept(clazz, method, codeAttribute, oldOffset, this); + } + else if (!deleted[oldOffset]) + { + if (DEBUG) + { + System.out.println(" Copied "+instruction.toString(newOffset)); + } + + // Remap the instruction. + instruction.accept(clazz, method, codeAttribute, oldOffset, this); + } + + // Remap and insert the post-inserted instruction, if any. + Instruction postInstruction = postInsertions[oldOffset]; + if (postInstruction != null) + { + if (DEBUG) + { + System.out.println(" Post-inserted "+postInstruction.toString(newOffset)); + } + + // Remap the instruction. + postInstruction.accept(clazz, method, codeAttribute, oldOffset, this); + } + } + + + // Implementations for InstructionVisitor. + + public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) + { + // Write out the instruction. + instructionWriter.visitSimpleInstruction(clazz, + method, + codeAttribute, + newOffset, + simpleInstruction); + + newOffset += simpleInstruction.length(newOffset); + } + + + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) + { + // Write out the instruction. + instructionWriter.visitConstantInstruction(clazz, + method, + codeAttribute, + newOffset, + constantInstruction); + + newOffset += constantInstruction.length(newOffset); + } + + + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) + { + // Write out the instruction. + instructionWriter.visitVariableInstruction(clazz, + method, + codeAttribute, + newOffset, + variableInstruction); + + newOffset += variableInstruction.length(newOffset); + } + + + public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) + { + // Adjust the branch offset. + branchInstruction.branchOffset = remapBranchOffset(offset, + branchInstruction.branchOffset); + + // Write out the instruction. + instructionWriter.visitBranchInstruction(clazz, + method, + codeAttribute, + newOffset, + branchInstruction); + + newOffset += branchInstruction.length(newOffset); + } + + + public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction) + { + // Adjust the default jump offset. + tableSwitchInstruction.defaultOffset = remapBranchOffset(offset, + tableSwitchInstruction.defaultOffset); + + // Adjust the jump offsets. + remapJumpOffsets(offset, + tableSwitchInstruction.jumpOffsets); + + // Write out the instruction. + instructionWriter.visitTableSwitchInstruction(clazz, + method, + codeAttribute, + newOffset, + tableSwitchInstruction); + + newOffset += tableSwitchInstruction.length(newOffset); + } + + + public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) + { + // Adjust the default jump offset. + lookUpSwitchInstruction.defaultOffset = remapBranchOffset(offset, + lookUpSwitchInstruction.defaultOffset); + + // Adjust the jump offsets. + remapJumpOffsets(offset, + lookUpSwitchInstruction.jumpOffsets); + + // Write out the instruction. + instructionWriter.visitLookUpSwitchInstruction(clazz, + method, + codeAttribute, + newOffset, + lookUpSwitchInstruction); + + newOffset += lookUpSwitchInstruction.length(newOffset); + } + + + // 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); + exceptionInfo.u2handlerPC = remapInstructionOffset(exceptionInfo.u2handlerPC); + } + + + // 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. + localVariableInfo.u2length = remapBranchOffset(localVariableInfo.u2startPC, + localVariableInfo.u2length); + localVariableInfo.u2startPC = remapInstructionOffset(localVariableInfo.u2startPC); + } + + + // 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. + localVariableTypeInfo.u2length = remapBranchOffset(localVariableTypeInfo.u2startPC, + localVariableTypeInfo.u2length); + localVariableTypeInfo.u2startPC = remapInstructionOffset(localVariableTypeInfo.u2startPC); + } + + + // Small utility methods. + + /** + * 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 offset + * with the given branch offset. + */ + private int remapBranchOffset(int offset, int branchOffset) + { + return remapInstructionOffset(offset + branchOffset) - newOffset; + } + + + /** + * Computes the new instruction offset for the instruction at the given offset. + */ + private int remapInstructionOffset(int offset) + { + if (offset < 0 || + offset > codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+offset+"] in code with length ["+codeLength+"]"); + } + + return instructionOffsetMap[offset]; + } + + + /** + * 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; + } + } + + 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; + } + } + + 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; + } + } + + 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; + } + } + + return newIndex; + } + + + private class CompositeInstruction + extends Instruction + { + private Instruction[] instructions; + + + private CompositeInstruction(Instruction[] instructions) + { + this.instructions = instructions; + } + + + // Implementations for Instruction. + + public Instruction shrink() + { + for (int index = 0; index < instructions.length; index++) + { + instructions[index] = instructions[index].shrink(); + } + + return this; + } + + + public void write(byte[] code, int offset) + { + for (int index = 0; index < instructions.length; index++) + { + Instruction instruction = instructions[index]; + + instruction.write(code, offset); + + offset += instruction.length(offset); + } + } + + + protected void readInfo(byte[] code, int offset) + { + throw new UnsupportedOperationException("Can't read composite instruction"); + } + + + protected void writeInfo(byte[] code, int offset) + { + throw new UnsupportedOperationException("Can't write composite instruction"); + } + + + public int length(int offset) + { + int newOffset = offset; + + for (int index = 0; index < instructions.length; index++) + { + newOffset += instructions[index].length(newOffset); + } + + return newOffset - offset; + } + + + public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor) + { + if (instructionVisitor != CodeAttributeEditor.this) + { + throw new UnsupportedOperationException("Unexpected visitor ["+instructionVisitor+"]"); + } + + for (int index = 0; index < instructions.length; index++) + { + Instruction instruction = instructions[index]; + + instruction.accept(clazz, method, codeAttribute, offset, CodeAttributeEditor.this); + + offset += instruction.length(offset); + } + } + + + // Implementations for Object. + + public String toString() + { + StringBuffer stringBuffer = new StringBuffer(); + + for (int index = 0; index < instructions.length; index++) + { + stringBuffer.append(instructions[index].toString()).append("; "); + } + + return stringBuffer.toString(); + } + } +} |