1
/* $Id: GotoCommonCodeReplacer.java,v 1.2 2005/10/04 21:00:11 eric Exp $
3
* ProGuard -- shrinking, optimization, and obfuscation of Java class files.
5
* Copyright (c) 2002-2005 Eric Lafortune (eric@graphics.cornell.edu)
7
* This program is free software; you can redistribute it and/or modify it
8
* under the terms of the GNU General Public License as published by the Free
9
* Software Foundation; either version 2 of the License, or (at your option)
12
* This program is distributed in the hope that it will be useful, but WITHOUT
13
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17
* You should have received a copy of the GNU General Public License along
18
* with this program; if not, write to the Free Software Foundation, Inc.,
19
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
package proguard.optimize.peephole;
23
import proguard.classfile.*;
24
import proguard.classfile.attribute.CodeAttrInfo;
25
import proguard.classfile.editor.CodeAttrInfoEditor;
26
import proguard.classfile.instruction.*;
29
* This InstructionVisitor redirects unconditional branches so any common code
30
* is shared, and the code preceding the branch can be removed.
32
* @author Eric Lafortune
34
public class GotoCommonCodeReplacer implements InstructionVisitor
36
private static final boolean DEBUG = false;
39
private BranchTargetFinder branchTargetFinder;
40
private CodeAttrInfoEditor codeAttrInfoEditor;
41
private InstructionVisitor extraInstructionVisitor;
45
* Creates a new GotoCommonCodeReplacer.
46
* @param branchTargetFinder a branch target finder that has been
47
* initialized to indicate branch targets
48
* in the visited code.
49
* @param codeAttrInfoEditor a code editor that can be used for
50
* accumulating changes to the code.
52
public GotoCommonCodeReplacer(BranchTargetFinder branchTargetFinder,
53
CodeAttrInfoEditor codeAttrInfoEditor)
55
this(branchTargetFinder, codeAttrInfoEditor, null);
60
* Creates a new GotoCommonCodeReplacer.
61
* @param branchTargetFinder a branch target finder that has been
62
* initialized to indicate branch targets
63
* in the visited code.
64
* @param codeAttrInfoEditor a code editor that can be used for
65
* accumulating changes to the code.
66
* @param extraInstructionVisitor an optional extra visitor for all replaced
69
public GotoCommonCodeReplacer(BranchTargetFinder branchTargetFinder,
70
CodeAttrInfoEditor codeAttrInfoEditor,
71
InstructionVisitor extraInstructionVisitor)
73
this.branchTargetFinder = branchTargetFinder;
74
this.codeAttrInfoEditor = codeAttrInfoEditor;
75
this.extraInstructionVisitor = extraInstructionVisitor;
79
// Implementations for InstructionVisitor.
81
public void visitSimpleInstruction(ClassFile classFile, MethodInfo methodInfo, CodeAttrInfo codeAttrInfo, int offset, SimpleInstruction simpleInstruction) {}
82
public void visitCpInstruction(ClassFile classFile, MethodInfo methodInfo, CodeAttrInfo codeAttrInfo, int offset, CpInstruction cpInstruction) {}
83
public void visitVariableInstruction(ClassFile classFile, MethodInfo methodInfo, CodeAttrInfo codeAttrInfo, int offset, VariableInstruction variableInstruction) {}
84
public void visitTableSwitchInstruction(ClassFile classFile, MethodInfo methodInfo, CodeAttrInfo codeAttrInfo, int offset, TableSwitchInstruction tableSwitchInstruction) {}
85
public void visitLookUpSwitchInstruction(ClassFile classFile, MethodInfo methodInfo, CodeAttrInfo codeAttrInfo, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) {}
88
public void visitBranchInstruction(ClassFile classFile, MethodInfo methodInfo, CodeAttrInfo codeAttrInfo, int offset, BranchInstruction branchInstruction)
90
// Check if the instruction is an unconditional goto instruction that
91
// isn't the target of a branch itself.
92
byte opcode = branchInstruction.opcode;
93
if ((opcode == InstructionConstants.OP_GOTO ||
94
opcode == InstructionConstants.OP_GOTO_W) &&
95
!branchTargetFinder.isBranchTarget(offset))
97
int branchOffset = branchInstruction.branchOffset;
98
int targetOffset = offset + branchOffset;
100
// Get the number of common bytes.
101
int commonCount = commonByteCodeCount(codeAttrInfo, offset, targetOffset);
103
if (commonCount > 0 &&
104
!exceptionBoundary(codeAttrInfo, offset, targetOffset))
108
System.out.println("GotoCommonCodeReplacer: "+classFile.getName()+"."+methodInfo.getName(classFile)+" ("+commonCount+" instructions)");
111
// Delete the common instructions.
112
for (int delta = 0; delta <= commonCount; delta++)
114
int deleteOffset = offset - delta;
115
if (branchTargetFinder.isInstruction(deleteOffset))
117
codeAttrInfoEditor.replaceInstruction( deleteOffset, null);
118
codeAttrInfoEditor.insertBeforeInstruction(deleteOffset, null);
119
codeAttrInfoEditor.insertAfterInstruction( deleteOffset, null);
121
codeAttrInfoEditor.deleteInstruction(deleteOffset);
125
// Redirect the goto instruction, if it is still necessary.
126
int newBranchOffset = branchOffset - commonCount;
127
if (newBranchOffset != branchInstruction.length(offset))
129
Instruction newGotoInstruction =
130
new BranchInstruction(opcode, newBranchOffset);
131
codeAttrInfoEditor.replaceInstruction(offset,
135
// Visit the instruction, if required.
136
if (extraInstructionVisitor != null)
138
extraInstructionVisitor.visitBranchInstruction(classFile, methodInfo, codeAttrInfo, offset, branchInstruction);
145
// Small utility methods.
148
* Returns the number of common bytes preceding the given offsets,
149
* avoiding branches and exception blocks.
151
private int commonByteCodeCount(CodeAttrInfo codeAttrInfo, int offset1, int offset2)
153
// Find the block of common instructions preceding it.
154
byte[] code = codeAttrInfo.code;
156
int successfulDelta = 0;
161
offset2 - delta != offset1;
164
int newOffset1 = offset1 - delta;
165
int newOffset2 = offset2 - delta;
167
// Is the code identical at both offsets?
168
if (code[newOffset1] != code[newOffset2])
173
// Are there instructions at either offset but not both?
174
if (branchTargetFinder.isInstruction(newOffset1) ^
175
branchTargetFinder.isInstruction(newOffset2))
180
// Are there instructions at both offsets?
181
if (branchTargetFinder.isInstruction(newOffset1) &&
182
branchTargetFinder.isInstruction(newOffset2))
184
// Are the offsets involved in some branches?
185
// Note that the preverifier also doesn't like
186
// initializer invocations to be moved around.
187
if (branchTargetFinder.isBranchOrigin(newOffset1) ||
188
branchTargetFinder.isBranchTarget(newOffset1) ||
189
branchTargetFinder.isExceptionStart(newOffset1) ||
190
branchTargetFinder.isExceptionEnd(newOffset1) ||
191
branchTargetFinder.isInitializer(newOffset1) ||
192
branchTargetFinder.isExceptionStart(newOffset2) ||
193
branchTargetFinder.isExceptionEnd(newOffset2))
198
successfulDelta = delta;
202
return successfulDelta;
207
* Returns the whether there is a boundary of an exception block between
208
* the given offsets (including both).
210
private boolean exceptionBoundary(CodeAttrInfo codeAttrInfo, int offset1, int offset2)
212
// Swap the offsets if the second one is smaller than the first one.
213
if (offset2 < offset1)
215
int offset = offset1;
220
// Check if there is a boundary of an exception block.
221
for (int offset = offset1; offset <= offset2; offset++)
223
if (branchTargetFinder.isExceptionStart(offset) ||
224
branchTargetFinder.isExceptionEnd(offset))