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