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