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.evaluation;
22 
23 import proguard.classfile.*;
24 import proguard.classfile.attribute.*;
25 import proguard.classfile.attribute.visitor.*;
26 import proguard.classfile.editor.*;
27 import proguard.classfile.util.*;
28 import proguard.classfile.visitor.MemberVisitor;
29 
30 /**
31  * This AttributeVisitor optimizes variable allocation based on their the liveness,
32  * in the code attributes that it visits.
33  *
34  * @author Eric Lafortune
35  */
36 public class VariableOptimizer
37 extends      SimplifiedVisitor
38 implements   AttributeVisitor,
39              LocalVariableInfoVisitor,
40              LocalVariableTypeInfoVisitor
41 {
42     //*
43     private static final boolean DEBUG = false;
44     /*/
45     private static       boolean DEBUG = true;
46     //*/
47 
48     private static final int MAX_VARIABLES_SIZE = 64;
49 
50 
51     private final boolean       reuseThis;
52     private final MemberVisitor extraVariableMemberVisitor;
53 
54     private final LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer();
55     private final VariableRemapper variableRemapper = new VariableRemapper();
56     private       VariableCleaner  variableCleaner  = new VariableCleaner();
57 
58     private int[] variableMap = new int[ClassConstants.TYPICAL_VARIABLES_SIZE];
59 
60 
61     /**
62      * Creates a new VariableOptimizer.
63      * @param reuseThis specifies whether the 'this' variable can be reused.
64      *                  Many JVMs for JME and IBM's JVMs for JSE can't handle
65      *                  such reuse.
66      */
VariableOptimizer(boolean reuseThis)67     public VariableOptimizer(boolean reuseThis)
68     {
69         this(reuseThis, null);
70     }
71 
72 
73     /**
74      * Creates a new VariableOptimizer with an extra visitor.
75      * @param reuseThis                  specifies whether the 'this' variable
76      *                                   can be reused. Many JVMs for JME and
77      *                                   IBM's JVMs for JSE can't handle such
78      *                                   reuse.
79      * @param extraVariableMemberVisitor an optional extra visitor for all
80      *                                   removed variables.
81      */
VariableOptimizer(boolean reuseThis, MemberVisitor extraVariableMemberVisitor)82     public VariableOptimizer(boolean       reuseThis,
83                              MemberVisitor extraVariableMemberVisitor)
84     {
85         this.reuseThis                  = reuseThis;
86         this.extraVariableMemberVisitor = extraVariableMemberVisitor;
87     }
88 
89 
90     // Implementations for AttributeVisitor.
91 
visitAnyAttribute(Clazz clazz, Attribute attribute)92     public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
93 
94 
visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)95     public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
96     {
97 //        DEBUG =
98 //            clazz.getName().equals("abc/Def") &&
99 //            method.getName(clazz).equals("abc");
100 
101         // Initialize the global arrays.
102         initializeArrays(codeAttribute);
103 
104         // Analyze the liveness of the variables in the code.
105         livenessAnalyzer.visitCodeAttribute(clazz, method, codeAttribute);
106 
107         // Trim the variables in the local variable tables, because even
108         // clipping the tables individually may leave some inconsistencies
109         // between them.
110         codeAttribute.attributesAccept(clazz, method, this);
111 
112         int startIndex =
113             (method.getAccessFlags() & ClassConstants.ACC_STATIC) != 0 ||
114             reuseThis ? 0 : 1;
115 
116         int parameterSize =
117             ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz),
118                                                   method.getAccessFlags());
119 
120         int variableSize = codeAttribute.u2maxLocals;
121         int codeLength   = codeAttribute.u4codeLength;
122 
123         boolean remapping = false;
124 
125         // Loop over all variables.
126         for (int oldIndex = 0; oldIndex < variableSize; oldIndex++)
127         {
128             // By default, the variable will be mapped onto itself.
129             variableMap[oldIndex] = oldIndex;
130 
131             // Only try remapping the variable if it's not a parameter.
132             if (oldIndex >= parameterSize &&
133                 oldIndex < MAX_VARIABLES_SIZE)
134             {
135                 // Try to remap the variable to a variable with a smaller index.
136                 for (int newIndex = startIndex; newIndex < oldIndex; newIndex++)
137                 {
138                     if (areNonOverlapping(oldIndex, newIndex, codeLength))
139                     {
140                         variableMap[oldIndex] = newIndex;
141 
142                         updateLiveness(oldIndex, newIndex, codeLength);
143 
144                         remapping = true;
145 
146                         // This variable has been remapped. Go to the next one.
147                         break;
148                     }
149                 }
150             }
151         }
152 
153         // Have we been able to remap any variables?
154         if (remapping)
155         {
156             if (DEBUG)
157             {
158                 System.out.println("VariableOptimizer: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
159                 for (int index= 0; index < variableSize; index++)
160                 {
161                     System.out.println("  v"+index+" -> "+variableMap[index]);
162                 }
163             }
164 
165             // Remap the variables.
166             variableRemapper.setVariableMap(variableMap);
167             variableRemapper.visitCodeAttribute(clazz, method, codeAttribute);
168 
169             // Visit the method, if required.
170             if (extraVariableMemberVisitor != null)
171             {
172                 method.accept(clazz, extraVariableMemberVisitor);
173             }
174         }
175         else
176         {
177             // Just clean up any empty variables.
178             variableCleaner.visitCodeAttribute(clazz, method, codeAttribute);
179         }
180     }
181 
182 
visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)183     public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
184     {
185         // Trim the variables in the local variable table.
186         localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
187     }
188 
189 
visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)190     public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
191     {
192         // Trim the variables in the local variable type table.
193         localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
194     }
195 
196 
197     // Implementations for LocalVariableInfoVisitor.
198 
visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)199     public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
200     {
201         // Trim the local variable to the instructions at which it is alive.
202         int variable = localVariableInfo.u2index;
203         int startPC  = localVariableInfo.u2startPC;
204         int endPC    = startPC + localVariableInfo.u2length;
205 
206         startPC = firstLiveness(startPC, endPC, variable);
207         endPC   = lastLiveness(startPC, endPC, variable);
208 
209         // Leave the start address of unused variables unchanged.
210         int length = endPC - startPC;
211         if (length > 0)
212         {
213             localVariableInfo.u2startPC = startPC;
214         }
215 
216         localVariableInfo.u2length  = length;
217     }
218 
219 
220     // Implementations for LocalVariableTypeInfoVisitor.
221 
visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)222     public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
223     {
224         // Trim the local variable type to the instructions at which it is alive.
225         int variable = localVariableTypeInfo.u2index;
226         int startPC  = localVariableTypeInfo.u2startPC;
227         int endPC    = startPC + localVariableTypeInfo.u2length;
228 
229         startPC = firstLiveness(startPC, endPC, variable);
230         endPC   = lastLiveness(startPC, endPC, variable);
231 
232         // Leave the start address of unused variables unchanged.
233         int length = endPC - startPC;
234         if (length > 0)
235         {
236             localVariableTypeInfo.u2startPC = startPC;
237         }
238 
239         localVariableTypeInfo.u2length  = length;
240     }
241 
242 
243     // Small utility methods.
244 
245     /**
246      * Initializes the global arrays.
247      */
initializeArrays(CodeAttribute codeAttribute)248     private void initializeArrays(CodeAttribute codeAttribute)
249     {
250         int codeLength = codeAttribute.u4codeLength;
251 
252         // Create new arrays for storing information at each instruction offset.
253         if (variableMap.length < codeLength)
254         {
255             variableMap = new int[codeLength];
256         }
257     }
258 
259 
260     /**
261      * Returns whether the given variables are never alive at the same time.
262      */
areNonOverlapping(int variableIndex1, int variableIndex2, int codeLength)263     private boolean areNonOverlapping(int variableIndex1,
264                                       int variableIndex2,
265                                       int codeLength)
266     {
267         // Loop over all instructions.
268         for (int offset = 0; offset < codeLength; offset++)
269         {
270             if ((livenessAnalyzer.isAliveBefore(offset, variableIndex1) &&
271                  livenessAnalyzer.isAliveBefore(offset, variableIndex2)) ||
272 
273                 (livenessAnalyzer.isAliveAfter(offset, variableIndex1) &&
274                  livenessAnalyzer.isAliveAfter(offset, variableIndex2)) ||
275 
276                 // For now, exclude Category 2 variables.
277                 livenessAnalyzer.isCategory2(offset, variableIndex1))
278             {
279                 return false;
280             }
281         }
282 
283         return true;
284     }
285 
286 
287     /**
288      * Updates the liveness resulting from mapping the given old variable on
289      * the given new variable.
290      */
updateLiveness(int oldVariableIndex, int newVariableIndex, int codeLength)291     private void updateLiveness(int oldVariableIndex,
292                                 int newVariableIndex,
293                                 int codeLength)
294     {
295         // Loop over all instructions.
296         for (int offset = 0; offset < codeLength; offset++)
297         {
298             // Update the liveness before the instruction.
299             if (livenessAnalyzer.isAliveBefore(offset, oldVariableIndex))
300             {
301                 livenessAnalyzer.setAliveBefore(offset, oldVariableIndex, false);
302                 livenessAnalyzer.setAliveBefore(offset, newVariableIndex, true);
303             }
304 
305             // Update the liveness after the instruction.
306             if (livenessAnalyzer.isAliveAfter(offset, oldVariableIndex))
307             {
308                 livenessAnalyzer.setAliveAfter(offset, oldVariableIndex, false);
309                 livenessAnalyzer.setAliveAfter(offset, newVariableIndex, true);
310             }
311         }
312     }
313 
314 
315     /**
316      * Returns the first instruction offset between the given offsets at which
317      * the given variable goes alive.
318      */
firstLiveness(int startOffset, int endOffset, int variableIndex)319     private int firstLiveness(int startOffset, int endOffset, int variableIndex)
320     {
321         for (int offset = startOffset; offset < endOffset; offset++)
322         {
323             if (livenessAnalyzer.isTraced(offset) &&
324                 livenessAnalyzer.isAliveBefore(offset, variableIndex))
325             {
326                 return offset;
327             }
328         }
329 
330         return endOffset;
331     }
332 
333 
334     /**
335      * Returns the last instruction offset between the given offsets before
336      * which the given variable is still alive.
337      */
lastLiveness(int startOffset, int endOffset, int variableIndex)338     private int lastLiveness(int startOffset, int endOffset, int variableIndex)
339     {
340         int previousOffset = endOffset;
341 
342         for (int offset = endOffset-1; offset >= startOffset; offset--)
343         {
344             if (livenessAnalyzer.isTraced(offset))
345             {
346                 if (livenessAnalyzer.isAliveBefore(offset, variableIndex))
347                 {
348                     return previousOffset;
349                 }
350 
351                 previousOffset = offset;
352             }
353         }
354 
355         return endOffset;
356     }
357 }
358