1/* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2013 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 */ 21package proguard.shrink; 22 23import proguard.classfile.*; 24import proguard.classfile.attribute.*; 25import proguard.classfile.attribute.annotation.*; 26import proguard.classfile.attribute.annotation.visitor.*; 27import proguard.classfile.attribute.visitor.*; 28import proguard.classfile.constant.*; 29import proguard.classfile.editor.*; 30import proguard.classfile.util.*; 31import proguard.classfile.visitor.*; 32 33import java.util.Arrays; 34 35/** 36 * This ClassVisitor removes constant pool entries, class members, and other 37 * class elements that are not marked as being used. 38 * 39 * @see UsageMarker 40 * 41 * @author Eric Lafortune 42 */ 43public class ClassShrinker 44extends SimplifiedVisitor 45implements ClassVisitor, 46 MemberVisitor, 47 AttributeVisitor, 48 AnnotationVisitor, 49 ElementValueVisitor 50{ 51 private final UsageMarker usageMarker; 52 53 private int[] constantIndexMap = new int[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE]; 54 private final ConstantPoolRemapper constantPoolRemapper = new ConstantPoolRemapper(); 55 56 57 /** 58 * Creates a new ClassShrinker. 59 * @param usageMarker the usage marker that is used to mark the classes 60 * and class members. 61 */ 62 public ClassShrinker(UsageMarker usageMarker) 63 { 64 this.usageMarker = usageMarker; 65 } 66 67 68 // Implementations for ClassVisitor. 69 70 public void visitProgramClass(ProgramClass programClass) 71 { 72 // Shrink the arrays for constant pool, interfaces, fields, methods, 73 // and class attributes. 74 programClass.u2interfacesCount = 75 shrinkConstantIndexArray(programClass.constantPool, 76 programClass.u2interfaces, 77 programClass.u2interfacesCount); 78 79 // Shrinking the constant pool also sets up an index map. 80 int newConstantPoolCount = 81 shrinkConstantPool(programClass.constantPool, 82 programClass.u2constantPoolCount); 83 84 programClass.u2fieldsCount = 85 shrinkArray(programClass.fields, 86 programClass.u2fieldsCount); 87 88 programClass.u2methodsCount = 89 shrinkArray(programClass.methods, 90 programClass.u2methodsCount); 91 92 programClass.u2attributesCount = 93 shrinkArray(programClass.attributes, 94 programClass.u2attributesCount); 95 96 // Compact the remaining fields, methods, and attributes, 97 // and remap their references to the constant pool. 98 programClass.fieldsAccept(this); 99 programClass.methodsAccept(this); 100 programClass.attributesAccept(this); 101 102 // Remap the references to the constant pool if it has shrunk. 103 if (newConstantPoolCount < programClass.u2constantPoolCount) 104 { 105 programClass.u2constantPoolCount = newConstantPoolCount; 106 107 // Remap all constant pool references. 108 constantPoolRemapper.setConstantIndexMap(constantIndexMap); 109 constantPoolRemapper.visitProgramClass(programClass); 110 } 111 112 // Remove the unused interfaces from the class signature. 113 programClass.attributesAccept(new SignatureShrinker()); 114 115 // Compact the extra field pointing to the subclasses of this class. 116 programClass.subClasses = 117 shrinkToNewArray(programClass.subClasses); 118 } 119 120 121 public void visitLibraryClass(LibraryClass libraryClass) 122 { 123 // Library classes are left unchanged. 124 125 // Compact the extra field pointing to the subclasses of this class. 126 libraryClass.subClasses = 127 shrinkToNewArray(libraryClass.subClasses); 128 } 129 130 131 // Implementations for MemberVisitor. 132 133 public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) 134 { 135 // Shrink the attributes array. 136 programMember.u2attributesCount = 137 shrinkArray(programMember.attributes, 138 programMember.u2attributesCount); 139 140 // Shrink any attributes. 141 programMember.attributesAccept(programClass, this); 142 } 143 144 145 // Implementations for AttributeVisitor. 146 147 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 148 149 150 public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) 151 { 152 // Shrink the array of BootstrapMethodInfo objects. 153 bootstrapMethodsAttribute.u2bootstrapMethodsCount = 154 shrinkArray(bootstrapMethodsAttribute.bootstrapMethods, 155 bootstrapMethodsAttribute.u2bootstrapMethodsCount); 156 } 157 158 159 public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) 160 { 161 // Shrink the array of InnerClassesInfo objects. 162 innerClassesAttribute.u2classesCount = 163 shrinkArray(innerClassesAttribute.classes, 164 innerClassesAttribute.u2classesCount); 165 } 166 167 168 public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) 169 { 170 // Sometimes, a class is still referenced (apparently as a dummy class), 171 // but its enclosing method is not. Then remove the reference to 172 // the enclosing method. 173 // E.g. the anonymous inner class javax.swing.JList$1 is defined inside 174 // a constructor of javax.swing.JList, but it is also referenced as a 175 // dummy argument in a constructor of javax.swing.JList$ListSelectionHandler. 176 if (enclosingMethodAttribute.referencedMethod != null && 177 !usageMarker.isUsed(enclosingMethodAttribute.referencedMethod)) 178 { 179 enclosingMethodAttribute.u2nameAndTypeIndex = 0; 180 181 enclosingMethodAttribute.referencedMethod = null; 182 } 183 } 184 185 186 public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 187 { 188 // Shrink the attributes array. 189 codeAttribute.u2attributesCount = 190 shrinkArray(codeAttribute.attributes, 191 codeAttribute.u2attributesCount); 192 193 // Shrink the attributes themselves. 194 codeAttribute.attributesAccept(clazz, method, this); 195 } 196 197 198 public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) 199 { 200 // Shrink the local variable info array. 201 localVariableTableAttribute.u2localVariableTableLength = 202 shrinkArray(localVariableTableAttribute.localVariableTable, 203 localVariableTableAttribute.u2localVariableTableLength); 204 } 205 206 207 public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) 208 { 209 // Shrink the local variable type info array. 210 localVariableTypeTableAttribute.u2localVariableTypeTableLength = 211 shrinkArray(localVariableTypeTableAttribute.localVariableTypeTable, 212 localVariableTypeTableAttribute.u2localVariableTypeTableLength); 213 } 214 215 216 public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) 217 { 218 // Shrink the annotations array. 219 annotationsAttribute.u2annotationsCount = 220 shrinkArray(annotationsAttribute.annotations, 221 annotationsAttribute.u2annotationsCount); 222 223 // Shrink the annotations themselves. 224 annotationsAttribute.annotationsAccept(clazz, this); 225 } 226 227 228 public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) 229 { 230 // Loop over all parameters. 231 for (int parameterIndex = 0; parameterIndex < parameterAnnotationsAttribute.u2parametersCount; parameterIndex++) 232 { 233 // Shrink the parameter annotations array. 234 parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex] = 235 shrinkArray(parameterAnnotationsAttribute.parameterAnnotations[parameterIndex], 236 parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex]); 237 } 238 239 // Shrink the annotations themselves. 240 parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); 241 } 242 243 244 // Implementations for AnnotationVisitor. 245 246 public void visitAnnotation(Clazz clazz, Annotation annotation) 247 { 248 // Shrink the element values array. 249 annotation.u2elementValuesCount = 250 shrinkArray(annotation.elementValues, 251 annotation.u2elementValuesCount); 252 253 // Shrink the element values themselves. 254 annotation.elementValuesAccept(clazz, this); 255 } 256 257 258 /** 259 * This AttributeVisitor updates the Utf8 constants of class signatures, 260 * removing any unused interfaces. 261 */ 262 private class SignatureShrinker 263 extends SimplifiedVisitor 264 implements AttributeVisitor 265 { 266 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 267 268 269 public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) 270 { 271 Clazz[] referencedClasses = signatureAttribute.referencedClasses; 272 if (referencedClasses != null) 273 { 274 // Go over the generic definitions, superclass and implemented interfaces. 275 String signature = clazz.getString(signatureAttribute.u2signatureIndex); 276 277 InternalTypeEnumeration internalTypeEnumeration = 278 new InternalTypeEnumeration(signature); 279 280 StringBuffer newSignatureBuffer = new StringBuffer(); 281 282 int referencedClassIndex = 0; 283 int newReferencedClassIndex = 0; 284 285 while (internalTypeEnumeration.hasMoreTypes()) 286 { 287 // Consider the classes referenced by this signature. 288 String type = internalTypeEnumeration.nextType(); 289 int classCount = new DescriptorClassEnumeration(type).classCount(); 290 291 Clazz referencedClass = referencedClasses[referencedClassIndex]; 292 if (referencedClass == null || 293 usageMarker.isUsed(referencedClass)) 294 { 295 // Append the superclass or interface. 296 newSignatureBuffer.append(type); 297 298 // Copy the referenced classes. 299 for (int counter = 0; counter < classCount; counter++) 300 { 301 referencedClasses[newReferencedClassIndex++] = 302 referencedClasses[referencedClassIndex++]; 303 } 304 } 305 else 306 { 307 // Skip the referenced classes. 308 referencedClassIndex += classCount; 309 } 310 } 311 312 if (newReferencedClassIndex < referencedClassIndex) 313 { 314 // Update the signature. 315 ((Utf8Constant)((ProgramClass)clazz).constantPool[signatureAttribute.u2signatureIndex]).setString(newSignatureBuffer.toString()); 316 317 // Clear the unused entries. 318 while (newReferencedClassIndex < referencedClassIndex) 319 { 320 referencedClasses[newReferencedClassIndex++] = null; 321 } 322 } 323 } 324 } 325 } 326 327 328 // Implementations for ElementValueVisitor. 329 330 public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) {} 331 332 333 public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) 334 { 335 // Shrink the contained annotation. 336 annotationElementValue.annotationAccept(clazz, this); 337 } 338 339 340 public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) 341 { 342 // Shrink the element values array. 343 arrayElementValue.u2elementValuesCount = 344 shrinkArray(arrayElementValue.elementValues, 345 arrayElementValue.u2elementValuesCount); 346 347 // Shrink the element values themselves. 348 arrayElementValue.elementValuesAccept(clazz, annotation, this); 349 } 350 351 352 // Small utility methods. 353 354 /** 355 * Removes all entries that are not marked as being used from the given 356 * constant pool. 357 * @return the new number of entries. 358 */ 359 private int shrinkConstantPool(Constant[] constantPool, int length) 360 { 361 if (constantIndexMap.length < length) 362 { 363 constantIndexMap = new int[length]; 364 } 365 366 int counter = 1; 367 boolean isUsed = false; 368 369 // Shift the used constant pool entries together. 370 for (int index = 1; index < length; index++) 371 { 372 constantIndexMap[index] = counter; 373 374 Constant constant = constantPool[index]; 375 376 // Don't update the flag if this is the second half of a long entry. 377 if (constant != null) 378 { 379 isUsed = usageMarker.isUsed(constant); 380 } 381 382 if (isUsed) 383 { 384 constantPool[counter++] = constant; 385 } 386 } 387 388 // Clear the remaining constant pool elements. 389 Arrays.fill(constantPool, counter, length, null); 390 391 return counter; 392 } 393 394 395 /** 396 * Removes all indices that point to unused constant pool entries 397 * from the given array. 398 * @return the new number of indices. 399 */ 400 private int shrinkConstantIndexArray(Constant[] constantPool, int[] array, int length) 401 { 402 int counter = 0; 403 404 // Shift the used objects together. 405 for (int index = 0; index < length; index++) 406 { 407 if (usageMarker.isUsed(constantPool[array[index]])) 408 { 409 array[counter++] = array[index]; 410 } 411 } 412 413 // Clear the remaining array elements. 414 Arrays.fill(array, counter, length, 0); 415 416 return counter; 417 } 418 419 420 /** 421 * Removes all Clazz objects that are not marked as being used 422 * from the given array and returns the remaining objects in a an array 423 * of the right size. 424 * @return the new array. 425 */ 426 private Clazz[] shrinkToNewArray(Clazz[] array) 427 { 428 if (array == null) 429 { 430 return null; 431 } 432 433 // Shrink the given array in-place. 434 int length = shrinkArray(array, array.length); 435 if (length == 0) 436 { 437 return null; 438 } 439 440 // Return immediately if the array is of right size already. 441 if (length == array.length) 442 { 443 return array; 444 } 445 446 // Copy the remaining elements into a new array of the right size. 447 Clazz[] newArray = new Clazz[length]; 448 System.arraycopy(array, 0, newArray, 0, length); 449 return newArray; 450 } 451 452 453 /** 454 * Removes all VisitorAccepter objects that are not marked as being used 455 * from the given array. 456 * @return the new number of VisitorAccepter objects. 457 */ 458 private int shrinkArray(VisitorAccepter[] array, int length) 459 { 460 int counter = 0; 461 462 // Shift the used objects together. 463 for (int index = 0; index < length; index++) 464 { 465 if (usageMarker.isUsed(array[index])) 466 { 467 array[counter++] = array[index]; 468 } 469 } 470 471 // Clear any remaining array elements. 472 if (counter < length) 473 { 474 Arrays.fill(array, counter, length, null); 475 } 476 477 return counter; 478 } 479} 480