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 */
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       int[]                   bootstrapMethodIndexMap = new int[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE];
55    private final ConstantPoolRemapper    constantPoolRemapper    = new ConstantPoolRemapper();
56    private final BootstrapMethodRemapper bootstrapMethodRemapper = new BootstrapMethodRemapper();
57
58
59    /**
60     * Creates a new ClassShrinker.
61     * @param usageMarker the usage marker that is used to mark the classes
62     *                    and class members.
63     */
64    public ClassShrinker(UsageMarker usageMarker)
65    {
66        this.usageMarker = usageMarker;
67    }
68
69
70    // Implementations for ClassVisitor.
71
72    public void visitProgramClass(ProgramClass programClass)
73    {
74        // Shrink the arrays for constant pool, interfaces, fields, methods,
75        // and class attributes.
76        if (programClass.u2interfacesCount > 0)
77        {
78            new InterfaceDeleter(shrinkFlags(programClass.constantPool,
79                                             programClass.u2interfaces,
80                                             programClass.u2interfacesCount))
81                .visitProgramClass(programClass);
82        }
83
84        // Shrinking the constant pool also sets up an index map.
85        int newConstantPoolCount =
86            shrinkConstantPool(programClass.constantPool,
87                               programClass.u2constantPoolCount);
88
89        programClass.u2fieldsCount =
90            shrinkArray(programClass.fields,
91                        programClass.u2fieldsCount);
92
93        programClass.u2methodsCount =
94            shrinkArray(programClass.methods,
95                        programClass.u2methodsCount);
96
97        programClass.u2attributesCount =
98            shrinkArray(programClass.attributes,
99                        programClass.u2attributesCount);
100
101        // Compact the remaining fields, methods, and attributes,
102        // and remap their references to the constant pool.
103        programClass.fieldsAccept(this);
104        programClass.methodsAccept(this);
105        programClass.attributesAccept(this);
106
107        // Remap the references to the constant pool if it has shrunk.
108        if (newConstantPoolCount < programClass.u2constantPoolCount)
109        {
110            programClass.u2constantPoolCount = newConstantPoolCount;
111
112            // Remap all constant pool references.
113            constantPoolRemapper.setConstantIndexMap(constantIndexMap);
114            constantPoolRemapper.visitProgramClass(programClass);
115        }
116
117        // Replace any unused classes in the signatures.
118        MySignatureCleaner signatureCleaner = new MySignatureCleaner();
119        programClass.fieldsAccept(new AllAttributeVisitor(signatureCleaner));
120        programClass.methodsAccept(new AllAttributeVisitor(signatureCleaner));
121        programClass.attributesAccept(signatureCleaner);
122
123        // Compact the extra field pointing to the subclasses of this class.
124        programClass.subClasses =
125            shrinkToNewArray(programClass.subClasses);
126    }
127
128
129    public void visitLibraryClass(LibraryClass libraryClass)
130    {
131        // Library classes are left unchanged.
132
133        // Compact the extra field pointing to the subclasses of this class.
134        libraryClass.subClasses =
135            shrinkToNewArray(libraryClass.subClasses);
136    }
137
138
139    // Implementations for MemberVisitor.
140
141    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
142    {
143        // Shrink the attributes array.
144        programMember.u2attributesCount =
145            shrinkArray(programMember.attributes,
146                        programMember.u2attributesCount);
147
148        // Shrink any attributes.
149        programMember.attributesAccept(programClass, this);
150    }
151
152
153    // Implementations for AttributeVisitor.
154
155    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
156
157
158    public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute)
159    {
160        // Shrink the array of BootstrapMethodInfo objects.
161        int newBootstrapMethodsCount =
162            shrinkBootstrapMethodArray(bootstrapMethodsAttribute.bootstrapMethods,
163                                       bootstrapMethodsAttribute.u2bootstrapMethodsCount);
164
165        if (newBootstrapMethodsCount < bootstrapMethodsAttribute.u2bootstrapMethodsCount)
166        {
167            bootstrapMethodsAttribute.u2bootstrapMethodsCount = newBootstrapMethodsCount;
168
169            // Remap all constant pool references.
170            bootstrapMethodRemapper.setConstantIndexMap(bootstrapMethodIndexMap);
171            clazz.constantPoolEntriesAccept(bootstrapMethodRemapper);
172        }
173    }
174
175
176    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
177    {
178        // Shrink the array of InnerClassesInfo objects.
179        innerClassesAttribute.u2classesCount =
180            shrinkArray(innerClassesAttribute.classes,
181                        innerClassesAttribute.u2classesCount);
182    }
183
184
185    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
186    {
187        // Sometimes, a class is still referenced (apparently as a dummy class),
188        // but its enclosing method is not. Then remove the reference to
189        // the enclosing method.
190        // E.g. the anonymous inner class javax.swing.JList$1 is defined inside
191        // a constructor of javax.swing.JList, but it is also referenced as a
192        // dummy argument in a constructor of javax.swing.JList$ListSelectionHandler.
193        if (enclosingMethodAttribute.referencedMethod != null &&
194            !usageMarker.isUsed(enclosingMethodAttribute.referencedMethod))
195        {
196            enclosingMethodAttribute.u2nameAndTypeIndex = 0;
197
198            enclosingMethodAttribute.referencedMethod = null;
199        }
200    }
201
202
203    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
204    {
205        // Shrink the attributes array.
206        codeAttribute.u2attributesCount =
207            shrinkArray(codeAttribute.attributes,
208                        codeAttribute.u2attributesCount);
209
210        // Shrink the attributes themselves.
211        codeAttribute.attributesAccept(clazz, method, this);
212    }
213
214
215    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
216    {
217        // Shrink the local variable info array.
218        localVariableTableAttribute.u2localVariableTableLength =
219            shrinkArray(localVariableTableAttribute.localVariableTable,
220                        localVariableTableAttribute.u2localVariableTableLength);
221    }
222
223
224    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
225    {
226        // Shrink the local variable type info array.
227        localVariableTypeTableAttribute.u2localVariableTypeTableLength =
228            shrinkArray(localVariableTypeTableAttribute.localVariableTypeTable,
229                        localVariableTypeTableAttribute.u2localVariableTypeTableLength);
230    }
231
232
233    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
234    {
235        // Shrink the annotations array.
236        annotationsAttribute.u2annotationsCount =
237            shrinkArray(annotationsAttribute.annotations,
238                        annotationsAttribute.u2annotationsCount);
239
240        // Shrink the annotations themselves.
241        annotationsAttribute.annotationsAccept(clazz, this);
242    }
243
244
245    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
246    {
247        // Loop over all parameters.
248        for (int parameterIndex = 0; parameterIndex < parameterAnnotationsAttribute.u1parametersCount; parameterIndex++)
249        {
250            // Shrink the parameter annotations array.
251            parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex] =
252                shrinkArray(parameterAnnotationsAttribute.parameterAnnotations[parameterIndex],
253                            parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex]);
254        }
255
256        // Shrink the annotations themselves.
257        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
258    }
259
260
261    // Implementations for AnnotationVisitor.
262
263    public void visitAnnotation(Clazz clazz, Annotation annotation)
264    {
265        // Shrink the element values array.
266        annotation.u2elementValuesCount =
267            shrinkArray(annotation.elementValues,
268                        annotation.u2elementValuesCount);
269
270        // Shrink the element values themselves.
271        annotation.elementValuesAccept(clazz, this);
272    }
273
274
275    /**
276     * This AttributeVisitor updates the Utf8 constants of signatures
277     * of classes, fields, and methods.
278     */
279    private class MySignatureCleaner
280    extends       SimplifiedVisitor
281    implements    AttributeVisitor
282    {
283        public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
284
285
286        public void visitSignatureAttribute(Clazz clazz, SignatureAttribute  signatureAttribute)
287        {
288            Clazz[] referencedClasses = signatureAttribute.referencedClasses;
289            if (referencedClasses != null)
290            {
291                // Go over the classes in the signature.
292                String signature = signatureAttribute.getSignature(clazz);
293
294                DescriptorClassEnumeration classEnumeration =
295                    new DescriptorClassEnumeration(signature);
296
297                int referencedClassIndex = 0;
298
299                // Start construction a new signature.
300                StringBuffer newSignatureBuffer = new StringBuffer();
301
302                newSignatureBuffer.append(classEnumeration.nextFluff());
303
304                while (classEnumeration.hasMoreClassNames())
305                {
306                    String className = classEnumeration.nextClassName();
307
308                    // Replace the class name if it is unused.
309                    Clazz referencedClass = referencedClasses[referencedClassIndex];
310                    if (referencedClass != null &&
311                        !usageMarker.isUsed(referencedClass))
312                    {
313                        className = ClassConstants.NAME_JAVA_LANG_OBJECT;
314
315                        referencedClasses[referencedClassIndex] = null;
316                    }
317
318                    referencedClassIndex++;
319
320                    newSignatureBuffer.append(className);
321                    newSignatureBuffer.append(classEnumeration.nextFluff());
322                }
323
324                // Update the signature.
325                ((Utf8Constant)((ProgramClass)clazz).constantPool[signatureAttribute.u2signatureIndex]).setString(newSignatureBuffer.toString());
326            }
327        }
328    }
329
330
331    // Implementations for ElementValueVisitor.
332
333    public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) {}
334
335
336    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
337    {
338        // Shrink the contained annotation.
339        annotationElementValue.annotationAccept(clazz, this);
340    }
341
342
343    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
344    {
345        // Shrink the element values array.
346        arrayElementValue.u2elementValuesCount =
347            shrinkArray(arrayElementValue.elementValues,
348                        arrayElementValue.u2elementValuesCount);
349
350        // Shrink the element values themselves.
351        arrayElementValue.elementValuesAccept(clazz, annotation, this);
352    }
353
354
355    // Small utility methods.
356
357    /**
358     * Removes all entries that are not marked as being used from the given
359     * constant pool. Creates a map from the old indices to the new indices
360     * as a side effect.
361     * @return the new number of entries.
362     */
363    private int shrinkConstantPool(Constant[] constantPool, int length)
364    {
365        if (constantIndexMap.length < length)
366        {
367            constantIndexMap = new int[length];
368        }
369
370        int     counter = 1;
371        boolean isUsed  = false;
372
373        // Shift the used constant pool entries together.
374        for (int index = 1; index < length; index++)
375        {
376            constantIndexMap[index] = counter;
377
378            Constant constant = constantPool[index];
379
380            // Is the constant being used? Don't update the flag if this is the
381            // second half of a long entry.
382            if (constant != null)
383            {
384                isUsed = usageMarker.isUsed(constant);
385            }
386
387            if (isUsed)
388            {
389                // Remember the new index.
390                constantIndexMap[index] = counter;
391
392                // Shift the constant pool entry.
393                constantPool[counter++] = constant;
394            }
395            else
396            {
397                // Remember an invalid index.
398                constantIndexMap[index] = -1;
399            }
400        }
401
402        // Clear the remaining constant pool elements.
403        Arrays.fill(constantPool, counter, length, null);
404
405        return counter;
406    }
407
408
409    /**
410     * Creates an array marking unused constant pool entries for all the
411     * elements in the given array of constant pool indices.
412     * @return an array of flags indicating unused elements.
413     */
414    private boolean[] shrinkFlags(Constant[] constantPool, int[] array, int length)
415    {
416        boolean[] unused = new boolean[length];
417
418        // Shift the used objects together.
419        for (int index = 0; index < length; index++)
420        {
421            if (!usageMarker.isUsed(constantPool[array[index]]))
422            {
423                unused[index] = true;
424            }
425        }
426
427        return unused;
428    }
429
430
431    /**
432     * Removes all indices that point to unused constant pool entries
433     * from the given array.
434     * @return the new number of indices.
435     */
436    private int shrinkConstantIndexArray(Constant[] constantPool, int[] array, int length)
437    {
438        int counter = 0;
439
440        // Shift the used objects together.
441        for (int index = 0; index < length; index++)
442        {
443            if (usageMarker.isUsed(constantPool[array[index]]))
444            {
445                array[counter++] = array[index];
446            }
447        }
448
449        // Clear the remaining array elements.
450        Arrays.fill(array, counter, length, 0);
451
452        return counter;
453    }
454
455
456    /**
457     * Removes all Clazz objects that are not marked as being used
458     * from the given array and returns the remaining objects in a an array
459     * of the right size.
460     * @return the new array.
461     */
462    private Clazz[] shrinkToNewArray(Clazz[] array)
463    {
464        if (array == null)
465        {
466            return null;
467        }
468
469        // Shrink the given array in-place.
470        int length = shrinkArray(array, array.length);
471        if (length == 0)
472        {
473            return null;
474        }
475
476        // Return immediately if the array is of right size already.
477        if (length == array.length)
478        {
479            return array;
480        }
481
482        // Copy the remaining elements into a new array of the right size.
483        Clazz[] newArray = new Clazz[length];
484        System.arraycopy(array, 0, newArray, 0, length);
485        return newArray;
486    }
487
488
489    /**
490     * Removes all entries that are not marked as being used from the given
491     * array of bootstrap methods. Creates a map from the old indices to the
492     * new indices as a side effect.
493     * @return the new number of entries.
494     */
495    private int shrinkBootstrapMethodArray(BootstrapMethodInfo[] bootstrapMethods, int length)
496    {
497        if (bootstrapMethodIndexMap.length < length)
498        {
499            bootstrapMethodIndexMap = new int[length];
500        }
501
502        int counter = 0;
503
504        // Shift the used bootstrap methods together.
505        for (int index = 0; index < length; index++)
506        {
507            BootstrapMethodInfo bootstrapMethod = bootstrapMethods[index];
508
509            // Is the entry being used?
510            if (usageMarker.isUsed(bootstrapMethod))
511            {
512                // Remember the new index.
513                bootstrapMethodIndexMap[index] = counter;
514
515                // Shift the entry.
516                bootstrapMethods[counter++] = bootstrapMethod;
517            }
518            else
519            {
520                // Remember an invalid index.
521                bootstrapMethodIndexMap[index] = -1;
522            }
523        }
524
525        // Clear the remaining bootstrap methods.
526        Arrays.fill(bootstrapMethods, counter, length, null);
527
528        return counter;
529    }
530
531
532    /**
533     * Removes all VisitorAccepter objects that are not marked as being used
534     * from the given array.
535     * @return the new number of VisitorAccepter objects.
536     */
537    private int shrinkArray(VisitorAccepter[] array, int length)
538    {
539        int counter = 0;
540
541        // Shift the used objects together.
542        for (int index = 0; index < length; index++)
543        {
544            VisitorAccepter visitorAccepter = array[index];
545
546            if (usageMarker.isUsed(visitorAccepter))
547            {
548                array[counter++] = visitorAccepter;
549            }
550        }
551
552        // Clear any remaining array elements.
553        if (counter < length)
554        {
555            Arrays.fill(array, counter, length, null);
556        }
557
558        return counter;
559    }
560}
561