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.classfile.editor;
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.constant.visitor.ConstantVisitor;
30import proguard.classfile.util.*;
31import proguard.classfile.visitor.*;
32
33/**
34 * This ClassVisitor fixes references of constant pool entries, fields,
35 * methods, and attributes to classes whose names have changed. Descriptors
36 * of member references are not updated yet.
37 *
38 * @see MemberReferenceFixer
39 * @author Eric Lafortune
40 */
41public class ClassReferenceFixer
42extends      SimplifiedVisitor
43implements   ClassVisitor,
44             ConstantVisitor,
45             MemberVisitor,
46             AttributeVisitor,
47             InnerClassesInfoVisitor,
48             LocalVariableInfoVisitor,
49             LocalVariableTypeInfoVisitor,
50             AnnotationVisitor,
51             ElementValueVisitor
52{
53    private final boolean ensureUniqueMemberNames;
54
55
56    /**
57     * Creates a new ClassReferenceFixer.
58     * @param ensureUniqueMemberNames specifies whether class members whose
59     *                                descriptor changes should get new, unique
60     *                                names, in order to avoid naming conflicts
61     *                                with similar methods.
62     */
63    public ClassReferenceFixer(boolean ensureUniqueMemberNames)
64    {
65        this.ensureUniqueMemberNames = ensureUniqueMemberNames;
66    }
67
68
69    // Implementations for ClassVisitor.
70
71    public void visitProgramClass(ProgramClass programClass)
72    {
73        // Fix the constant pool.
74        programClass.constantPoolEntriesAccept(this);
75
76        // Fix class members.
77        programClass.fieldsAccept(this);
78        programClass.methodsAccept(this);
79
80        // Fix the attributes.
81        programClass.attributesAccept(this);
82    }
83
84
85    public void visitLibraryClass(LibraryClass libraryClass)
86    {
87        // Fix class members.
88        libraryClass.fieldsAccept(this);
89        libraryClass.methodsAccept(this);
90    }
91
92
93    // Implementations for MemberVisitor.
94
95    public void visitProgramField(ProgramClass programClass, ProgramField programField)
96    {
97        // Has the descriptor changed?
98        String descriptor    = programField.getDescriptor(programClass);
99        String newDescriptor = newDescriptor(descriptor,
100                                             programField.referencedClass);
101
102        if (!descriptor.equals(newDescriptor))
103        {
104            ConstantPoolEditor constantPoolEditor =
105                new ConstantPoolEditor(programClass);
106
107            // Update the descriptor.
108            programField.u2descriptorIndex =
109                constantPoolEditor.addUtf8Constant(newDescriptor);
110
111            // Update the name, if requested.
112            if (ensureUniqueMemberNames)
113            {
114                String name    = programField.getName(programClass);
115                String newName = newUniqueMemberName(name, descriptor);
116                programField.u2nameIndex =
117                    constantPoolEditor.addUtf8Constant(newName);
118            }
119        }
120
121        // Fix the attributes.
122        programField.attributesAccept(programClass, this);
123    }
124
125
126    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
127    {
128        // Has the descriptor changed?
129        String descriptor    = programMethod.getDescriptor(programClass);
130        String newDescriptor = newDescriptor(descriptor,
131                                             programMethod.referencedClasses);
132
133        if (!descriptor.equals(newDescriptor))
134        {
135            ConstantPoolEditor constantPoolEditor =
136                new ConstantPoolEditor(programClass);
137
138            // Update the descriptor.
139            programMethod.u2descriptorIndex =
140                constantPoolEditor.addUtf8Constant(newDescriptor);
141
142            // Update the name, if requested.
143            if (ensureUniqueMemberNames)
144            {
145                String name    = programMethod.getName(programClass);
146                String newName = newUniqueMemberName(name, descriptor);
147                programMethod.u2nameIndex =
148                    constantPoolEditor.addUtf8Constant(newName);
149            }
150        }
151
152        // Fix the attributes.
153        programMethod.attributesAccept(programClass, this);
154    }
155
156
157    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
158    {
159        // Has the descriptor changed?
160        String descriptor    = libraryField.getDescriptor(libraryClass);
161        String newDescriptor = newDescriptor(descriptor,
162                                             libraryField.referencedClass);
163
164        // Update the descriptor.
165        libraryField.descriptor = newDescriptor;
166    }
167
168
169    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
170    {
171        // Has the descriptor changed?
172        String descriptor    = libraryMethod.getDescriptor(libraryClass);
173        String newDescriptor = newDescriptor(descriptor,
174                                             libraryMethod.referencedClasses);
175
176        // Update the descriptor.
177        libraryMethod.descriptor = newDescriptor;
178    }
179
180
181    // Implementations for ConstantVisitor.
182
183    public void visitAnyConstant(Clazz clazz, Constant constant) {}
184
185
186    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
187    {
188        // Does the string refer to a class, due to a Class.forName construct?
189        Clazz  referencedClass  = stringConstant.referencedClass;
190        Member referencedMember = stringConstant.referencedMember;
191        if (referencedClass  != null &&
192            referencedMember == null)
193        {
194            // Reconstruct the new class name.
195            String externalClassName    = stringConstant.getString(clazz);
196            String internalClassName    = ClassUtil.internalClassName(externalClassName);
197            String newInternalClassName = newClassName(internalClassName,
198                                                       referencedClass);
199
200            // Update the String entry if required.
201            if (!newInternalClassName.equals(internalClassName))
202            {
203                String newExternalClassName = ClassUtil.externalClassName(newInternalClassName);
204
205                // Refer to a new Utf8 entry.
206                stringConstant.u2stringIndex =
207                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newExternalClassName);
208            }
209        }
210    }
211
212
213    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
214    {
215        // Do we know the referenced class?
216        Clazz referencedClass = classConstant.referencedClass;
217        if (referencedClass != null)
218        {
219            // Has the class name changed?
220            String className    = classConstant.getName(clazz);
221            String newClassName = newClassName(className, referencedClass);
222            if (!className.equals(newClassName))
223            {
224                // Refer to a new Utf8 entry.
225                classConstant.u2nameIndex =
226                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName);
227            }
228        }
229    }
230
231    // Implementations for AttributeVisitor.
232
233    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
234
235
236    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
237    {
238        // Fix the inner class names.
239        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
240    }
241
242
243    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
244    {
245        // Fix the attributes.
246        codeAttribute.attributesAccept(clazz, method, this);
247    }
248
249
250    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
251    {
252        // Fix the types of the local variables.
253        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
254    }
255
256
257    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
258    {
259        // Fix the signatures of the local variables.
260        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
261    }
262
263
264    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
265    {
266        // Compute the new signature.
267        String signature    = clazz.getString(signatureAttribute.u2signatureIndex);
268        String newSignature = newDescriptor(signature,
269                                            signatureAttribute.referencedClasses);
270
271        if (!signature.equals(newSignature))
272        {
273            signatureAttribute.u2signatureIndex =
274                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature);
275        }
276    }
277
278
279    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
280    {
281        // Fix the annotations.
282        annotationsAttribute.annotationsAccept(clazz, this);
283    }
284
285
286    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
287    {
288        // Fix the annotations.
289        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
290    }
291
292
293    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
294    {
295        // Fix the annotation.
296        annotationDefaultAttribute.defaultValueAccept(clazz, this);
297    }
298
299
300    // Implementations for InnerClassesInfoVisitor.
301
302    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
303    {
304        // Fix the inner class name.
305        int innerClassIndex = innerClassesInfo.u2innerClassIndex;
306        int innerNameIndex  = innerClassesInfo.u2innerNameIndex;
307        if (innerClassIndex != 0 &&
308            innerNameIndex  != 0)
309        {
310            String newInnerName = clazz.getClassName(innerClassIndex);
311            int index = newInnerName.lastIndexOf(ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR);
312            if (index >= 0)
313            {
314                innerClassesInfo.u2innerNameIndex =
315                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newInnerName.substring(index + 1));
316            }
317        }
318    }
319
320
321    // Implementations for LocalVariableInfoVisitor.
322
323    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
324    {
325        // Has the descriptor changed?
326        String descriptor    = clazz.getString(localVariableInfo.u2descriptorIndex);
327        String newDescriptor = newDescriptor(descriptor,
328                                             localVariableInfo.referencedClass);
329
330        if (!descriptor.equals(newDescriptor))
331        {
332            // Refer to a new Utf8 entry.
333            localVariableInfo.u2descriptorIndex =
334                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor);
335        }
336    }
337
338    // Implementations for LocalVariableTypeInfoVisitor.
339
340    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
341    {
342        // Has the signature changed?
343        String signature    = clazz.getString(localVariableTypeInfo.u2signatureIndex);
344        String newSignature = newDescriptor(signature,
345                                            localVariableTypeInfo.referencedClasses);
346
347        if (!signature.equals(newSignature))
348        {
349            localVariableTypeInfo.u2signatureIndex =
350                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature);
351        }
352    }
353
354    // Implementations for AnnotationVisitor.
355
356    public void visitAnnotation(Clazz clazz, Annotation annotation)
357    {
358        // Compute the new type name.
359        String typeName    = clazz.getString(annotation.u2typeIndex);
360        String newTypeName = newDescriptor(typeName,
361                                           annotation.referencedClasses);
362
363        if (!typeName.equals(newTypeName))
364        {
365            // Refer to a new Utf8 entry.
366            annotation.u2typeIndex =
367                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName);
368        }
369
370        // Fix the element values.
371        annotation.elementValuesAccept(clazz, this);
372    }
373
374
375    // Implementations for ElementValueVisitor.
376
377    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
378    {
379    }
380
381
382    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
383    {
384        // Compute the new type name.
385        String typeName    = clazz.getString(enumConstantElementValue.u2typeNameIndex);
386        String newTypeName = newDescriptor(typeName,
387                                           enumConstantElementValue.referencedClasses);
388
389        if (!typeName.equals(newTypeName))
390        {
391            // Refer to a new Utf8 entry.
392            enumConstantElementValue.u2typeNameIndex =
393                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName);
394        }
395    }
396
397
398    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
399    {
400        // Compute the new class name.
401        String className    = clazz.getString(classElementValue.u2classInfoIndex);
402        String newClassName = newDescriptor(className,
403                                            classElementValue.referencedClasses);
404
405        if (!className.equals(newClassName))
406        {
407            // Refer to a new Utf8 entry.
408            classElementValue.u2classInfoIndex =
409                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName);
410        }
411    }
412
413
414    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
415    {
416        // Fix the annotation.
417        annotationElementValue.annotationAccept(clazz, this);
418    }
419
420
421    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
422    {
423        // Fix the element values.
424        arrayElementValue.elementValuesAccept(clazz, annotation, this);
425    }
426
427
428    // Small utility methods.
429
430    private static String newDescriptor(String descriptor,
431                                        Clazz  referencedClass)
432    {
433        // If there is no referenced class, the descriptor won't change.
434        if (referencedClass == null)
435        {
436            return descriptor;
437        }
438
439        // Unravel and reconstruct the class element of the descriptor.
440        DescriptorClassEnumeration descriptorClassEnumeration =
441            new DescriptorClassEnumeration(descriptor);
442
443        StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length());
444        newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff());
445
446        // Only if the descriptor contains a class name (e.g. with an array of
447        // primitive types), the descriptor can change.
448        if (descriptorClassEnumeration.hasMoreClassNames())
449        {
450            String className = descriptorClassEnumeration.nextClassName();
451            String fluff     = descriptorClassEnumeration.nextFluff();
452
453            String newClassName = newClassName(className,
454                                               referencedClass);
455
456            newDescriptorBuffer.append(newClassName);
457            newDescriptorBuffer.append(fluff);
458        }
459
460        return newDescriptorBuffer.toString();
461    }
462
463
464    private static String newDescriptor(String  descriptor,
465                                        Clazz[] referencedClasses)
466    {
467        // If there are no referenced classes, the descriptor won't change.
468        if (referencedClasses == null ||
469            referencedClasses.length == 0)
470        {
471            return descriptor;
472        }
473
474        // Unravel and reconstruct the class elements of the descriptor.
475        DescriptorClassEnumeration descriptorClassEnumeration =
476            new DescriptorClassEnumeration(descriptor);
477
478        StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length());
479        newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff());
480
481        int index = 0;
482        while (descriptorClassEnumeration.hasMoreClassNames())
483        {
484            String  className        = descriptorClassEnumeration.nextClassName();
485            boolean isInnerClassName = descriptorClassEnumeration.isInnerClassName();
486            String  fluff            = descriptorClassEnumeration.nextFluff();
487
488            String newClassName = newClassName(className,
489                                               referencedClasses[index++]);
490
491            // Strip the outer class name again, if it's an inner class.
492            if (isInnerClassName)
493            {
494                newClassName =
495                    newClassName.substring(newClassName.lastIndexOf(ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR)+1);
496            }
497
498            newDescriptorBuffer.append(newClassName);
499            newDescriptorBuffer.append(fluff);
500        }
501
502        return newDescriptorBuffer.toString();
503    }
504
505
506    /**
507     * Returns a unique class member name, based on the given name and descriptor.
508     */
509    private String newUniqueMemberName(String name, String descriptor)
510    {
511        return name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ?
512            ClassConstants.INTERNAL_METHOD_NAME_INIT :
513            name + ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode()));
514    }
515
516
517    /**
518     * Returns the new class name based on the given class name and the new
519     * name of the given referenced class. Class names of array types
520     * are handled properly.
521     */
522    private static String newClassName(String className,
523                                       Clazz  referencedClass)
524    {
525        // If there is no referenced class, the class name won't change.
526        if (referencedClass == null)
527        {
528            return className;
529        }
530
531        // Reconstruct the class name.
532        String newClassName = referencedClass.getName();
533
534        // Is it an array type?
535        if (className.charAt(0) == ClassConstants.INTERNAL_TYPE_ARRAY)
536        {
537            // Add the array prefixes and suffix "[L...;".
538            newClassName =
539                 className.substring(0, className.indexOf(ClassConstants.INTERNAL_TYPE_CLASS_START)+1) +
540                 newClassName +
541                 ClassConstants.INTERNAL_TYPE_CLASS_END;
542        }
543
544        return newClassName;
545    }
546}
547