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

proguard.classfile.editor.CodeAttributeEditor Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.6
Show newest version
/*
 * ProGuardCORE -- library to process Java bytecode.
 *
 * Copyright (c) 2002-2020 Guardsquare NV
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package proguard.classfile.editor;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.annotation.*;
import proguard.classfile.attribute.annotation.target.*;
import proguard.classfile.attribute.annotation.target.visitor.*;
import proguard.classfile.attribute.annotation.visitor.TypeAnnotationVisitor;
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.util.ArrayUtil;

import java.util.*;

/**
 * This {@link AttributeVisitor} accumulates specified changes to code, and then applies
 * these accumulated changes to the code attributes that it visits.
 * 

* The class also supports labels ({@link #label()}) and exception handlers * ({@link #catch_(int,int,int)}) in replacement sequences. They provide * local branch offsets inside the replacement sequences * ({@link Label#offset()}). For example, replacing a specified instruction * in a method by a sequence of instructions, with the help of * {@link InstructionSequenceBuilder}: *

 *     Clazz         clazz         = ...
 *     Method        method        = ...
 *     CodeAttribute codeAttribute = ...
 *     int           offset        = ...
 *
 *     CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
 *
 *     InstructionSequenceBuilder builder =
 *         new InstructionSequenceBuilder(clazz);
 *
 *     // Create labels and instructions.
 *     final CodeAttributeEditor.Label TRY_START = codeAttributeEditor.label();
 *     final CodeAttributeEditor.Label TRY_END   = codeAttributeEditor.label();
 *     final CodeAttributeEditor.Label CATCH_END = codeAttributeEditor.label();
 *
 *     final CodeAttributeEditor.Label CATCH_EXCEPTION =
 *         codeAttributeEditor.catch_(TRY_START.offset(),
 *                                    TRY_END.offset(),
 *                                    constantPoolEditor.addClassConstant("java/lang/Exception", null));
 *
 *     Instructions[] replacementInstructions = builder
 *         .label(TRY_START)
 *         ......
 *         .label(TRY_END)
 *         .goto_(CATCH_END.offset())
 *         .catch_(CATCH_EXCEPTION)
 *         ......
 *         .athrow()
 *         .label(CATCH_END)
 *         ......
 *         .instructions();
 *
 *     // Prepare the editor for this code.
 *     codeAttributeEditor.reset(codeAttribute.u4codeLength);
 *
 *     // Edit the code -- in this case, replace an instruction.
 *     codeAttributeEditor.replaceInstruction(offset, replacementInstructions);
 *
 *     // Apply the changes.
 *     codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
 * 
* * @author Eric Lafortune */ public class CodeAttributeEditor implements AttributeVisitor, InstructionVisitor, ExceptionInfoVisitor, StackMapFrameVisitor, VerificationTypeVisitor, LineNumberInfoVisitor, LocalVariableInfoVisitor, LocalVariableTypeInfoVisitor, TypeAnnotationVisitor, TargetInfoVisitor, LocalVariableTargetElementVisitor { //* private static final boolean DEBUG = false; /*/ public static boolean DEBUG = System.getProperty("cae") != null; //*/ private static final int LABEL_FLAG = 0x20000000; private static final Logger logger = LogManager.getLogger(CodeAttributeEditor.class); private final boolean updateFrameSizes; private final boolean shrinkInstructions; private int codeLength; private boolean modified; private boolean simple; private Map labels = new HashMap(); /*private*/public Instruction[] preOffsetInsertions = new Instruction[ClassEstimates.TYPICAL_CODE_LENGTH]; /*private*/public Instruction[] preInsertions = new Instruction[ClassEstimates.TYPICAL_CODE_LENGTH]; /*private*/public Instruction[] replacements = new Instruction[ClassEstimates.TYPICAL_CODE_LENGTH]; /*private*/public Instruction[] postInsertions = new Instruction[ClassEstimates.TYPICAL_CODE_LENGTH]; /*private*/public boolean[] deleted = new boolean[ClassEstimates.TYPICAL_CODE_LENGTH]; private int[] newInstructionOffsets = new int[ClassEstimates.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(); /** * Creates a new CodeAttributeEditor that automatically updates frame * sizes and shrinks instructions. */ public CodeAttributeEditor() { this(true, true); } /** * Creates a new CodeAttributeEditor. * @param updateFrameSizes specifies whether frame sizes of edited code * should be updated. * @param shrinkInstructions specifies whether added instructions should * automatically be shrunk before being written. */ public CodeAttributeEditor(boolean updateFrameSizes, boolean shrinkInstructions) { this.updateFrameSizes = updateFrameSizes; this.shrinkInstructions = shrinkInstructions; } /** * Resets the accumulated code changes for a given anticipated maximum * code length. If necessary, the size may still be extended while * editing the code, with {@link #extend(int)}. * @param codeLength the length of the code that will be edited next. */ public void reset(int codeLength) { labels.clear(); // Try to reuse the previous arrays. if (preInsertions.length < codeLength) { preOffsetInsertions = new Instruction[codeLength]; preInsertions = new Instruction[codeLength]; replacements = new Instruction[codeLength]; postInsertions = new Instruction[codeLength]; deleted = new boolean[codeLength]; } else { Arrays.fill(preOffsetInsertions, 0, codeLength, null); Arrays.fill(preInsertions, 0, codeLength, null); Arrays.fill(replacements, 0, codeLength, null); Arrays.fill(postInsertions, 0, codeLength, null); Arrays.fill(deleted, 0, codeLength, false); } this.codeLength = codeLength; modified = false; simple = true; } /** * Extends the size of the accumulated code changes. * @param codeLength the length of the code that will be edited next. */ public void extend(int codeLength) { // Try to reuse the previous arrays. if (preInsertions.length < codeLength) { preOffsetInsertions = ArrayUtil.extendArray(preOffsetInsertions, codeLength); preInsertions = ArrayUtil.extendArray(preInsertions, codeLength); replacements = ArrayUtil.extendArray(replacements, codeLength); postInsertions = ArrayUtil.extendArray(postInsertions, codeLength); deleted = ArrayUtil.extendArray(deleted, codeLength); } else { Arrays.fill(preOffsetInsertions, this.codeLength, codeLength, null); Arrays.fill(preInsertions, this.codeLength, codeLength, null); Arrays.fill(replacements, this.codeLength, codeLength, null); Arrays.fill(postInsertions, this.codeLength, codeLength, null); Arrays.fill(deleted, this.codeLength, codeLength, false); } this.codeLength = codeLength; } /** * Remembers to place the given instruction right before the instruction * at the given offset. Any branches to the existing instruction will * land after the new instruction. Similarly, any try blocks that start at * the existing instruction will not include the new instruction. However, * any try blocks that end right before the existing instruction wil now * include the new instruction. * @param instructionOffset the offset of the instruction. * @param instruction the new instruction. */ public void insertBeforeOffset(int instructionOffset, Instruction instruction) { if (instructionOffset < 0 || instructionOffset >= codeLength) { throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); } preOffsetInsertions[instructionOffset] = shrinkInstructions ? instruction.shrink() : instruction; modified = true; simple = false; } /** * Remembers to place the given instruction right before the instruction * at the given offset. Any branches to the existing instruction will * also go to the new instruction. Similarly, any try blocks that include * the existing instruction will also include the new instruction. * @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] = shrinkInstructions ? instruction.shrink() : instruction; modified = true; simple = false; } /** * Remembers to place the given instructions right before the instruction * at the given offset. Any branches to the existing instruction will * land after the new instructions. Similarly, any try blocks that start at * the existing instruction will not include the new instructions. However, * any try blocks that end right before the existing instruction wil now * include the new instructions. * @param instructionOffset the offset of the instruction. * @param instructions the new instructions. */ public void insertBeforeOffset(int instructionOffset, Instruction[] instructions) { if (instructionOffset < 0 || instructionOffset >= codeLength) { throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); } CompositeInstruction instruction = new CompositeInstruction(instructions); preOffsetInsertions[instructionOffset] = shrinkInstructions ? instruction.shrink() : instruction; modified = true; simple = false; } /** * Remembers to place the given instructions right before the instruction * at the given offset. Any branches to the existing instruction will * also go to the new instructions. Similarly, any try blocks that include * the existing instruction will also include the new instructions. * @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+"]"); } CompositeInstruction instruction = new CompositeInstruction(instructions); preInsertions[instructionOffset] = shrinkInstructions ? instruction.shrink() : instruction; 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] = shrinkInstructions ? instruction.shrink() : 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+"]"); } CompositeInstruction instruction = new CompositeInstruction(instructions); replacements[instructionOffset] = shrinkInstructions ? instruction.shrink() : instruction; 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] = shrinkInstructions ? instruction.shrink() : 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+"]"); } CompositeInstruction instruction = new CompositeInstruction(instructions); postInsertions[instructionOffset] = shrinkInstructions ? instruction.shrink() : instruction; 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; } /** * Clears all modifications of the instruction at the given offset. * @param instructionOffset the offset of the instruction to be deleted. */ public void clearModifications(int instructionOffset) { if (instructionOffset < 0 || instructionOffset >= codeLength) { throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); } preOffsetInsertions[instructionOffset] = null; preInsertions[instructionOffset] = null; replacements[instructionOffset] = null; postInsertions[instructionOffset] = null; deleted[instructionOffset] = false; } /** * Returns whether the code has been modified in any way. */ public boolean isModified() { return modified; } /** * Returns whether the instruction at the given offset has been modified * in any way. */ public boolean isModified(int instructionOffset) { return preOffsetInsertions[instructionOffset] != null || 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) { logger.error("Unexpected error while editing code:"); logger.error(" Class = [{}]", clazz.getName()); logger.error(" Method = [{}{}]", method.getName(clazz), method.getDescriptor(clazz)); logger.error(" Exception = [{}] ({})", ex.getClass().getName(), ex.getMessage()); throw ex; } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Do we have to update the code? if (modified) { if (DEBUG) { System.out.println("CodeAttributeEditor: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)); } // Can we perform a faster simple replacement of instructions? if (canPerformSimpleReplacements(codeAttribute)) { if (DEBUG) { System.out.println(" Simple editing"); } // Simply overwrite the instructions. performSimpleReplacements(codeAttribute); } else { if (DEBUG) { System.out.println(" Full editing"); } // Move and remap the instructions. codeAttribute.u4codeLength = updateInstructions(clazz, method, codeAttribute); // Update the exception table. codeAttribute.exceptionsAccept(clazz, method, this); // Remove exceptions with empty code blocks. codeAttribute.u2exceptionTableLength = removeEmptyExceptions(codeAttribute.exceptionTable, codeAttribute.u2exceptionTableLength); // Update the line number table and the local variable tables. codeAttribute.attributesAccept(clazz, method, this); } // Make sure instructions are widened if necessary. instructionWriter.visitCodeAttribute(clazz, method, codeAttribute); } // Update the maximum stack size and local variable frame size. if (updateFrameSizes) { stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); } } public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) { // Update all stack map entries. expectedStackMapFrameOffset = -1; stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); } public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) { // Update all stack map table entries. expectedStackMapFrameOffset = 0; stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { // Update 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) { // Update all local variable table entries. localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Update all local variable table entries. localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitAnyTypeAnnotationsAttribute(Clazz clazz, TypeAnnotationsAttribute typeAnnotationsAttribute) { typeAnnotationsAttribute.typeAnnotationsAccept(clazz, this); } /** * 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(offset)); } } } } /** * 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 (newInstructionOffsets.length < oldLength + 1) { newInstructionOffsets = 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. newInstructionOffsets[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) { // Account for the pre-offset-inserted instruction, if any. Instruction preOffsetInstruction = preOffsetInsertions[oldOffset]; if (preOffsetInstruction != null) { newOffset += preOffsetInstruction.length(newOffset); } newInstructionOffsets[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) { // Update and insert the pre-inserted instruction, if any. Instruction preOffsetInstruction = preOffsetInsertions[oldOffset]; if (preOffsetInstruction != null) { if (DEBUG) { System.out.println(" Pre-inserted ["+oldOffset+"] -> "+preOffsetInstruction.toString(clazz, newOffset)); } // Update the instruction. preOffsetInstruction.accept(clazz, method, codeAttribute, oldOffset, this); } // Update and insert the pre-inserted instruction, if any. Instruction preInstruction = preInsertions[oldOffset]; if (preInstruction != null) { if (DEBUG) { System.out.println(" Pre-inserted ["+oldOffset+"] -> "+preInstruction.toString(clazz, newOffset)); } // Update the instruction. preInstruction.accept(clazz, method, codeAttribute, oldOffset, this); } // Update 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 ["+oldOffset+"] -> "+replacementInstruction.toString(clazz, newOffset)); } // Update the instruction. replacementInstruction.accept(clazz, method, codeAttribute, oldOffset, this); } else if (!deleted[oldOffset]) { if (DEBUG) { System.out.println(" Copied ["+oldOffset+"] -> "+instruction.toString(clazz, newOffset)); } // Update the instruction. instruction.accept(clazz, method, codeAttribute, oldOffset, this); } // Update and insert the post-inserted instruction, if any. Instruction postInstruction = postInsertions[oldOffset]; if (postInstruction != null) { if (DEBUG) { System.out.println(" Post-inserted ["+oldOffset+"] -> "+postInstruction.toString(clazz, newOffset)); } // Update 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) { // Update the branch offset, relative to the precise new offset. branchInstruction.branchOffset = newBranchOffset(offset, branchInstruction.branchOffset, newOffset); // 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) { // Update the default jump offset, relative to the precise new offset. tableSwitchInstruction.defaultOffset = newBranchOffset(offset, tableSwitchInstruction.defaultOffset, newOffset); // Update the jump offsets, relative to the precise new offset. newJumpOffsets(offset, tableSwitchInstruction.jumpOffsets, newOffset); // 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) { // Update the default jump offset, relative to the precise new offset. lookUpSwitchInstruction.defaultOffset = newBranchOffset(offset, lookUpSwitchInstruction.defaultOffset, newOffset); // Update the jump offsets, relative to the precise new offset. newJumpOffsets(offset, lookUpSwitchInstruction.jumpOffsets, newOffset); // 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) { // Update 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); exceptionInfo.u2handlerPC = newInstructionOffset(exceptionInfo.u2handlerPC); } // Implementations for StackMapFrameVisitor. public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame) { // Update 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) { // Update the stack map frame offset. visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame); // Update the verification type offset. sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this); } public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame) { // Update the stack map frame offset. visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame); // Update the verification type offsets. moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this); } public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame) { // Update the stack map frame offset. visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame); // Update 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) { // Update 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) { // Update the code offset. lineNumberInfo.u2startPC = newInstructionOffset(lineNumberInfo.u2startPC); } // Implementations for LocalVariableInfoVisitor. public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { // Update the code offset and length. // Be careful to update the length first. localVariableInfo.u2length = newBranchOffset(localVariableInfo.u2startPC, localVariableInfo.u2length); localVariableInfo.u2startPC = newInstructionOffset(localVariableInfo.u2startPC); } // Implementations for LocalVariableTypeInfoVisitor. public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) { // Update the code offset and length. // Be careful to update the length first. localVariableTypeInfo.u2length = newBranchOffset(localVariableTypeInfo.u2startPC, localVariableTypeInfo.u2length); localVariableTypeInfo.u2startPC = newInstructionOffset(localVariableTypeInfo.u2startPC); } // Implementations for TypeAnnotationVisitor. public void visitTypeAnnotation(Clazz clazz, TypeAnnotation typeAnnotation) { // Update all local variable targets. typeAnnotation.targetInfoAccept(clazz, this); } // Implementations for TargetInfoVisitor. public void visitAnyTargetInfo(Clazz clazz, TypeAnnotation typeAnnotation, TargetInfo targetInfo) {} public void visitLocalVariableTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo) { // Update the offsets of the variables. localVariableTargetInfo.targetElementsAccept(clazz, method, codeAttribute, typeAnnotation, this); } public void visitOffsetTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, OffsetTargetInfo offsetTargetInfo) { // Update the offset. offsetTargetInfo.u2offset = newInstructionOffset(offsetTargetInfo.u2offset); } // Implementations for LocalVariableTargetElementVisitor. public void visitLocalVariableTargetElement(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo, LocalVariableTargetElement localVariableTargetElement) { // Update the variable start offset and length. // Be careful to update the length first. localVariableTargetElement.u2length = newBranchOffset(localVariableTargetElement.u2startPC, localVariableTargetElement.u2length); localVariableTargetElement.u2startPC = newInstructionOffset(localVariableTargetElement.u2startPC); } // Small utility methods. /** * Updates the given jump offsets for the instruction at the given offset, * relative to the given new offset. */ private void newJumpOffsets(int oldInstructionOffset, int[] oldJumpOffsets, int newInstructionOffset) { for (int index = 0; index < oldJumpOffsets.length; index++) { oldJumpOffsets[index] = newBranchOffset(oldInstructionOffset, oldJumpOffsets[index], newInstructionOffset); } } /** * Computes the new branch offset for the instruction at the given offset * with the given branch offset, relative to the new instruction (block) * offset. */ private int newBranchOffset(int oldInstructionOffset, int oldBranchOffset) { return newInstructionOffset(oldInstructionOffset + oldBranchOffset) - newInstructionOffset(oldInstructionOffset); } /** * Computes the new branch offset for the instruction at the given offset * with the given branch offset, relative to the given new offset. */ private int newBranchOffset(int oldInstructionOffset, int oldBranchOffset, int newInstructionOffset) { // Compute the old branch target. // Pass a label offset unchanged. int oldBranchTargetOffset = isLabel(oldBranchOffset) ? oldBranchOffset : oldInstructionOffset + oldBranchOffset; return newInstructionOffset(oldBranchTargetOffset) - newInstructionOffset; } /** * Computes the new instruction offset for the instruction at the given * offset. */ private int newInstructionOffset(int oldInstructionOffset) { // Special case: is it actually a label? if (isLabel(oldInstructionOffset)) { // Retrieve the new offset from the label. int labelIdentifier = labelIdentifier(oldInstructionOffset); Label label = (Label)labels.get(labelIdentifier); if (label == null) { throw new IllegalArgumentException("Reference to unknown label identifier ["+labelIdentifier+"]"); } return label.newOffset; } // Otherwise retrieve the new instruction offset. if (oldInstructionOffset < 0 || oldInstructionOffset > codeLength) { throw new IllegalArgumentException("Invalid instruction offset ["+oldInstructionOffset+"] in code with length ["+codeLength+"]"); } return newInstructionOffsets[oldInstructionOffset]; } /** * 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; } /** * This pseudo-instruction is a composite of other instructions, for local * use inside the editor class only. */ 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++) { if (canShrink(instructions[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); } } // Implementations for Object. @Override public String toString() { StringBuffer stringBuffer = new StringBuffer(); for (int index = 0; index < instructions.length; index++) { stringBuffer.append(instructions[index].toString()).append("; "); } return stringBuffer.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CompositeInstruction that = (CompositeInstruction)o; if (opcode != that.opcode) return false; if (instructions.length != that.instructions.length) return false; for (int index = 0; index < instructions.length; index++) { if (!instructions[index].equals(that.instructions[index])) return false; } return true; } @Override public int hashCode() { int result = Objects.hash(opcode); result = 31 * result + Arrays.hashCode(instructions); return result; } } // For convenience, we also define two pseudo-instructions, to conveniently // mark local labels and create new exceptions handlers. /** * Creates a new label that can be used as a pseudo-instruction to mark * a local offset. Its offset can be used as a branch target in * replacement instructions ({@link Label#offset()}). */ public Label label() { return label(labels.size()); } /** * Creates a new label that can be used as a pseudo-instruction to mark * a local offset. Its offset can be used as a branch target in * replacement instructions ({@link Label#offset()}). */ public Label label(int identifier) { Label label = new Label(identifier); // Remember the label, so we can retrieve its offset later on. labels.put(new Integer(identifier), label); return label; } /** * Creates a new catch instance that can be used as a pseudo-instruction * to mark the start of an exception handler. Its offset can be used as * a branch target in replacement instructions ({@link Label#offset()}). */ public Label catch_(int startOffset, int endOffset, int catchType) { return catch_(labels.size(), startOffset, endOffset, catchType); } /** * Creates a new catch instance that can be used as a pseudo-instruction * to mark the start of an exception handler. Its offset can be used as * a branch target in replacement instructions ({@link Label#offset()}). */ public Label catch_(int identifier, int startOffset, int endOffset, int catchType) { Label catch_ = new Catch(identifier, startOffset, endOffset, catchType); // Remember the label, so we can retrieve its offset later on. labels.put(new Integer(identifier), catch_); return catch_; } /** * Returns whether the given instruction offset actually represents a * label (which contains the actual offset). */ private static boolean isLabel(int instructionOffset) { return (instructionOffset & 0xff000000) == LABEL_FLAG; } /** * Returns the label identifier that corrresponds to the given * instruction offset. */ private static int labelIdentifier(int instructionOffset) { return instructionOffset & ~LABEL_FLAG; } /** * This pseudo-instruction represents a label that marks an instruction * offset, for use in the context of the code attribute editor only. */ public static class Label extends Instruction { protected final int identifier; private int newOffset; /** * Creates a new Label. * @param identifier an identifier that can be chosen freely. */ public Label(int identifier) { this.identifier = identifier; } /** * Returns the offset that can then be used as a branch target in * other replacement instructions. */ public int offset() { return LABEL_FLAG | identifier; } // Implementations for Instruction. public Instruction shrink() { return this; } public void write(byte[] code, int offset) { } protected void readInfo(byte[] code, int offset) { throw new UnsupportedOperationException("Can't read label instruction"); } protected void writeInfo(byte[] code, int offset) { throw new UnsupportedOperationException("Can't write label instruction"); } public int length(int offset) { // Remember the offset, so we can retrieve it later on. newOffset = offset; return 0; } public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor) { if (instructionVisitor.getClass() != CodeAttributeEditor.class) { throw new UnsupportedOperationException("Unexpected visitor ["+instructionVisitor+"]"); } } // Implementations for Object. @Override public String toString() { return "label_"+offset(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Label label = (Label)o; return opcode == label.opcode && identifier == label.identifier && newOffset == label.newOffset; } @Override public int hashCode() { return Objects.hash(opcode, identifier, newOffset); } } /** * This pseudo-instruction represents an exception handler, * for use in the context of the code attribute editor only. */ private static class Catch extends Label { private final int startOffset; private final int endOffset; private final int catchType; /** * Creates a new Catch instance. * @param identifier an identifier that can be chosen freely. * @param startOffset the start offset of the catch block. * @param endOffset the end offset of the catch block. * @param catchType the index of the catch type in the constant pool. */ public Catch(int identifier, int startOffset, int endOffset, int catchType) { super(identifier); this.startOffset = startOffset; this.endOffset = endOffset; this.catchType = catchType; } // Implementations for Instruction. public Instruction shrink() { return this; } public void write(byte[] code, int offset) { } protected void readInfo(byte[] code, int offset) { throw new UnsupportedOperationException("Can't read catch instruction"); } protected void writeInfo(byte[] code, int offset) { throw new UnsupportedOperationException("Can't write catch instruction"); } public int length(int offset) { return super.length(offset); } public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor) { if (instructionVisitor.getClass() != CodeAttributeEditor.class) { throw new UnsupportedOperationException("Unexpected visitor ["+instructionVisitor+"]"); } // Add the exception. Its offsets will still be updated later on, // like any other exception. new ExceptionInfoEditor(codeAttribute).prependException( new ExceptionInfo(startOffset, endOffset, offset(), catchType)); } // Implementations for Object. @Override public String toString() { return "catch " + (isLabel(startOffset) ? "label_" : "") + startOffset + ", " + (isLabel(endOffset) ? "label_" : "") + endOffset + ", #" + catchType; } @Override public boolean equals(Object o) { if (this == o) return true; if (!super.equals(o)) return false; Catch aCatch = (Catch)o; return startOffset == aCatch.startOffset && endOffset == aCatch.endOffset && catchType == aCatch.catchType; } @Override public int hashCode() { return Objects.hash(super.hashCode(), startOffset, endOffset, catchType); } } /** * calling shrink() on a branch instruction that branches to a label throws * an exception, because BranchInstruction doesn't know about labels. * * @param instruction An instruction. * @return true if shrink() can be called on the instruction */ private boolean canShrink(Instruction instruction) { return shrinkInstructions && !(instruction instanceof BranchInstruction && isLabel(((BranchInstruction)instruction).branchOffset)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy