1 /*
2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
3  *             of Java bytecode.
4  *
5  * Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu)
6  *
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)
10  * any later version.
11  *
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
15  * more details.
16  *
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
20  */
21 package proguard.optimize.peephole;
22 
23 import proguard.classfile.*;
24 import proguard.classfile.attribute.*;
25 import proguard.classfile.attribute.visitor.AttributeVisitor;
26 import proguard.classfile.editor.CodeAttributeEditor;
27 import proguard.classfile.instruction.*;
28 import proguard.classfile.instruction.visitor.InstructionVisitor;
29 import proguard.classfile.util.SimplifiedVisitor;
30 
31 /**
32  * This AttributeVisitor redirects unconditional branches so any common code
33  * is shared, and the code preceding the branch can be removed, in the code
34  * attributes that it visits.
35  *
36  * @author Eric Lafortune
37  */
38 public class GotoCommonCodeReplacer
39 extends      SimplifiedVisitor
40 implements   AttributeVisitor,
41              InstructionVisitor
42 {
43     //*
44     private static final boolean DEBUG = false;
45     /*/
46     private static       boolean DEBUG = true;
47     //*/
48 
49 
50     private final InstructionVisitor  extraInstructionVisitor;
51 
52     private final BranchTargetFinder  branchTargetFinder  = new BranchTargetFinder();
53     private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, false);
54 
55 
56     /**
57      * Creates a new GotoCommonCodeReplacer.
58      * @param extraInstructionVisitor an optional extra visitor for all replaced
59      *                                goto instructions.
60      */
GotoCommonCodeReplacer(InstructionVisitor extraInstructionVisitor)61     public GotoCommonCodeReplacer(InstructionVisitor  extraInstructionVisitor)
62     {
63         this.extraInstructionVisitor = extraInstructionVisitor;
64     }
65 
66 
67     // Implementations for AttributeVisitor.
68 
visitAnyAttribute(Clazz clazz, Attribute attribute)69     public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
70 
71 
visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)72     public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
73     {
74 //        DEBUG =
75 //            clazz.getName().equals("abc/Def") &&
76 //            method.getName(clazz).equals("abc");
77 
78         // Mark all branch targets.
79         branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute);
80 
81         // Reset the code attribute editor.
82         codeAttributeEditor.reset(codeAttribute.u4codeLength);
83 
84         // Remap the variables of the instructions.
85         codeAttribute.instructionsAccept(clazz, method, this);
86 
87         // Apply the code atribute editor.
88         codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
89     }
90 
91 
92     // Implementations for InstructionVisitor.
93 
visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)94     public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
95 
96 
visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)97     public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
98     {
99         // Check if the instruction is an unconditional goto instruction that
100         // isn't the target of a branch itself.
101         byte opcode = branchInstruction.opcode;
102         if ((opcode == InstructionConstants.OP_GOTO ||
103              opcode == InstructionConstants.OP_GOTO_W) &&
104             !branchTargetFinder.isBranchTarget(offset))
105         {
106             int branchOffset = branchInstruction.branchOffset;
107             int targetOffset = offset + branchOffset;
108 
109             // Get the number of common bytes.
110             int commonCount = commonByteCodeCount(codeAttribute, offset, targetOffset);
111 
112             if (commonCount > 0 &&
113                 !exceptionBoundary(codeAttribute, offset, targetOffset))
114             {
115                 if (DEBUG)
116                 {
117                     System.out.println("GotoCommonCodeReplacer: "+clazz.getName()+"."+method.getName(clazz)+" (["+(offset-commonCount)+"] - "+branchInstruction.toString(offset)+" -> "+targetOffset+")");
118                 }
119 
120                 // Delete the common instructions.
121                 for (int delta = 0; delta <= commonCount; delta++)
122                 {
123                     int deleteOffset = offset - delta;
124                     if (branchTargetFinder.isInstruction(deleteOffset))
125                     {
126                         codeAttributeEditor.clearModifications(deleteOffset);
127                         codeAttributeEditor.deleteInstruction(deleteOffset);
128                     }
129                 }
130 
131                 // Redirect the goto instruction, if it is still necessary.
132                 int newBranchOffset = branchOffset - commonCount;
133                 if (newBranchOffset != branchInstruction.length(offset))
134                 {
135                     Instruction newGotoInstruction =
136                          new BranchInstruction(opcode, newBranchOffset).shrink();
137                     codeAttributeEditor.replaceInstruction(offset,
138                                                            newGotoInstruction);
139                 }
140 
141                 // Visit the instruction, if required.
142                 if (extraInstructionVisitor != null)
143                 {
144                     extraInstructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction);
145                 }
146             }
147         }
148     }
149 
150 
151     // Small utility methods.
152 
153     /**
154      * Returns the number of common bytes preceding the given offsets,
155      * avoiding branches and exception blocks.
156      */
commonByteCodeCount(CodeAttribute codeAttribute, int offset1, int offset2)157     private int commonByteCodeCount(CodeAttribute codeAttribute, int offset1, int offset2)
158     {
159         // Find the block of common instructions preceding it.
160         byte[] code = codeAttribute.code;
161 
162         int successfulDelta = 0;
163 
164         for (int delta = 1;
165              delta <= offset1 &&
166              delta <= offset2 &&
167              offset2 - delta != offset1;
168              delta++)
169         {
170             int newOffset1 = offset1 - delta;
171             int newOffset2 = offset2 - delta;
172 
173             // Is the code identical at both offsets?
174             if (code[newOffset1] != code[newOffset2])
175             {
176                 break;
177             }
178 
179             // Are there instructions at either offset but not both?
180             if (branchTargetFinder.isInstruction(newOffset1) ^
181                 branchTargetFinder.isInstruction(newOffset2))
182             {
183                 break;
184             }
185 
186             // Are there instructions at both offsets?
187             if (branchTargetFinder.isInstruction(newOffset1) &&
188                 branchTargetFinder.isInstruction(newOffset2))
189             {
190                 // Are the offsets involved in some branches?
191                 // Note that the preverifier doesn't like initializer
192                 // invocations to be moved around.
193                 // Also note that the preverifier doesn't like pop instructions
194                 // that work on different operands.
195                 if (branchTargetFinder.isBranchOrigin(newOffset1)   ||
196                     branchTargetFinder.isBranchTarget(newOffset1)   ||
197                     branchTargetFinder.isExceptionStart(newOffset1) ||
198                     branchTargetFinder.isExceptionEnd(newOffset1)   ||
199                     branchTargetFinder.isInitializer(newOffset1)    ||
200                     branchTargetFinder.isExceptionStart(newOffset2) ||
201                     branchTargetFinder.isExceptionEnd(newOffset2)   ||
202                     isPop(code[newOffset1]))
203                 {
204                     break;
205                 }
206 
207                 // Make sure the new branch target was a branch target before,
208                 // in order not to introduce new entries in the stack map table.
209                 if (branchTargetFinder.isBranchTarget(newOffset2))
210                 {
211                     successfulDelta = delta;
212                 }
213 
214                 if (branchTargetFinder.isBranchTarget(newOffset1))
215                 {
216                     break;
217                 }
218             }
219         }
220 
221         return successfulDelta;
222     }
223 
224 
225     /**
226      * Returns whether the given opcode represents a pop instruction that must
227      * get a consistent type (pop, pop2, arraylength).
228      */
isPop(byte opcode)229     private boolean isPop(byte opcode)
230     {
231         return opcode == InstructionConstants.OP_POP  ||
232                opcode == InstructionConstants.OP_POP2 ||
233                opcode == InstructionConstants.OP_ARRAYLENGTH;
234     }
235 
236 
237     /**
238      * Returns the whether there is a boundary of an exception block between
239      * the given offsets (including both).
240      */
exceptionBoundary(CodeAttribute codeAttribute, int offset1, int offset2)241     private boolean exceptionBoundary(CodeAttribute codeAttribute, int offset1, int offset2)
242     {
243         // Swap the offsets if the second one is smaller than the first one.
244         if (offset2 < offset1)
245         {
246             int offset = offset1;
247             offset1 = offset2;
248             offset2 = offset;
249         }
250 
251         // Check if there is a boundary of an exception block.
252         for (int offset = offset1; offset <= offset2; offset++)
253         {
254             if (branchTargetFinder.isExceptionStart(offset) ||
255                 branchTargetFinder.isExceptionEnd(offset))
256             {
257                 return true;
258             }
259         }
260 
261         return false;
262     }
263 }
264