blob: a1f422fdf9dfd718649ad3e64a22ef0621d9908d [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package proguard.optimize.peephole;
import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.editor.CodeAttributeEditor;
import proguard.classfile.instruction.*;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.util.SimplifiedVisitor;
/**
* This AttributeVisitor redirects unconditional branches so any common code
* is shared, and the code preceding the branch can be removed, in the code
* attributes that it visits.
*
* @author Eric Lafortune
*/
public class GotoCommonCodeReplacer
extends SimplifiedVisitor
implements AttributeVisitor,
InstructionVisitor
{
//*
private static final boolean DEBUG = false;
/*/
private static boolean DEBUG = true;
//*/
private final InstructionVisitor extraInstructionVisitor;
private final BranchTargetFinder branchTargetFinder = new BranchTargetFinder();
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, false);
/**
* Creates a new GotoCommonCodeReplacer.
* @param extraInstructionVisitor an optional extra visitor for all replaced
* goto instructions.
*/
public GotoCommonCodeReplacer(InstructionVisitor extraInstructionVisitor)
{
this.extraInstructionVisitor = extraInstructionVisitor;
}
// 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");
// Mark all branch targets.
branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute);
// Reset the code attribute editor.
codeAttributeEditor.reset(codeAttribute.u4codeLength);
// Remap the variables of the instructions.
codeAttribute.instructionsAccept(clazz, method, this);
// Apply the code atribute editor.
codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
}
// Implementations for InstructionVisitor.
public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
{
// Check if the instruction is an unconditional goto instruction that
// isn't the target of a branch itself.
byte opcode = branchInstruction.opcode;
if ((opcode == InstructionConstants.OP_GOTO ||
opcode == InstructionConstants.OP_GOTO_W) &&
!branchTargetFinder.isBranchTarget(offset))
{
int branchOffset = branchInstruction.branchOffset;
int targetOffset = offset + branchOffset;
// Get the number of common bytes.
int commonCount = commonByteCodeCount(codeAttribute, offset, targetOffset);
if (commonCount > 0 &&
!exceptionBoundary(codeAttribute, offset, targetOffset))
{
if (DEBUG)
{
System.out.println("GotoCommonCodeReplacer: "+clazz.getName()+"."+method.getName(clazz)+" (["+(offset-commonCount)+"] - "+branchInstruction.toString(offset)+" -> "+targetOffset+")");
}
// Delete the common instructions.
for (int delta = 0; delta <= commonCount; delta++)
{
int deleteOffset = offset - delta;
if (branchTargetFinder.isInstruction(deleteOffset))
{
codeAttributeEditor.clearModifications(deleteOffset);
codeAttributeEditor.deleteInstruction(deleteOffset);
}
}
// Redirect the goto instruction, if it is still necessary.
int newBranchOffset = branchOffset - commonCount;
if (newBranchOffset != branchInstruction.length(offset))
{
Instruction newGotoInstruction =
new BranchInstruction(opcode, newBranchOffset).shrink();
codeAttributeEditor.replaceInstruction(offset,
newGotoInstruction);
}
// Visit the instruction, if required.
if (extraInstructionVisitor != null)
{
extraInstructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction);
}
}
}
}
// Small utility methods.
/**
* Returns the number of common bytes preceding the given offsets,
* avoiding branches and exception blocks.
*/
private int commonByteCodeCount(CodeAttribute codeAttribute, int offset1, int offset2)
{
// Find the block of common instructions preceding it.
byte[] code = codeAttribute.code;
int successfulDelta = 0;
for (int delta = 1;
delta <= offset1 &&
delta <= offset2 &&
offset2 - delta != offset1;
delta++)
{
int newOffset1 = offset1 - delta;
int newOffset2 = offset2 - delta;
// Is the code identical at both offsets?
if (code[newOffset1] != code[newOffset2])
{
break;
}
// Are there instructions at either offset but not both?
if (branchTargetFinder.isInstruction(newOffset1) ^
branchTargetFinder.isInstruction(newOffset2))
{
break;
}
// Are there instructions at both offsets?
if (branchTargetFinder.isInstruction(newOffset1) &&
branchTargetFinder.isInstruction(newOffset2))
{
// Are the offsets involved in some branches?
// Note that the preverifier doesn't like initializer
// invocations to be moved around.
// Also note that the preverifier doesn't like pop instructions
// that work on different operands.
if (branchTargetFinder.isBranchOrigin(newOffset1) ||
branchTargetFinder.isBranchTarget(newOffset1) ||
branchTargetFinder.isExceptionStart(newOffset1) ||
branchTargetFinder.isExceptionEnd(newOffset1) ||
branchTargetFinder.isInitializer(newOffset1) ||
branchTargetFinder.isExceptionStart(newOffset2) ||
branchTargetFinder.isExceptionEnd(newOffset2) ||
isPop(code[newOffset1]))
{
break;
}
// Make sure the new branch target was a branch target before,
// in order not to introduce new entries in the stack map table.
if (branchTargetFinder.isBranchTarget(newOffset2))
{
successfulDelta = delta;
}
if (branchTargetFinder.isBranchTarget(newOffset1))
{
break;
}
}
}
return successfulDelta;
}
/**
* Returns whether the given opcode represents a pop instruction that must
* get a consistent type (pop, pop2, arraylength).
*/
private boolean isPop(byte opcode)
{
return opcode == InstructionConstants.OP_POP ||
opcode == InstructionConstants.OP_POP2 ||
opcode == InstructionConstants.OP_ARRAYLENGTH;
}
/**
* Returns the whether there is a boundary of an exception block between
* the given offsets (including both).
*/
private boolean exceptionBoundary(CodeAttribute codeAttribute, int offset1, int offset2)
{
// Swap the offsets if the second one is smaller than the first one.
if (offset2 < offset1)
{
int offset = offset1;
offset1 = offset2;
offset2 = offset;
}
// Check if there is a boundary of an exception block.
for (int offset = offset1; offset <= offset2; offset++)
{
if (branchTargetFinder.isExceptionStart(offset) ||
branchTargetFinder.isExceptionEnd(offset))
{
return true;
}
}
return false;
}
}