proguard.classfile.editor.CodeAttributeEditor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of proguard-core Show documentation
Show all versions of proguard-core Show documentation
ProGuardCORE is a free library to read, analyze, modify, and write Java class files.
/*
* 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