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.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        if (!descriptor.equals(newDescriptor))
177        {
178            // Update the descriptor.
179            libraryMethod.descriptor = newDescriptor;
180        }
181    }
182
183
184    // Implementations for ConstantVisitor.
185
186    public void visitAnyConstant(Clazz clazz, Constant constant) {}
187
188
189    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
190    {
191        // Does the string refer to a class, due to a Class.forName construct?
192        Clazz  referencedClass  = stringConstant.referencedClass;
193        Member referencedMember = stringConstant.referencedMember;
194        if (referencedClass  != null &&
195            referencedMember == null)
196        {
197            // Reconstruct the new class name.
198            String externalClassName    = stringConstant.getString(clazz);
199            String internalClassName    = ClassUtil.internalClassName(externalClassName);
200            String newInternalClassName = newClassName(internalClassName,
201                                                       referencedClass);
202
203            // Update the String entry if required.
204            if (!newInternalClassName.equals(internalClassName))
205            {
206                // Only convert to an external class name if the original was
207                // an external class name too.
208                String newExternalClassName =
209                    externalClassName.indexOf(JavaConstants.PACKAGE_SEPARATOR) >= 0 ?
210                        ClassUtil.externalClassName(newInternalClassName) :
211                        newInternalClassName;
212
213                // Refer to a new Utf8 entry.
214                stringConstant.u2stringIndex =
215                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newExternalClassName);
216            }
217        }
218    }
219
220
221    public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant)
222    {
223        // Has the descriptor changed?
224        String descriptor    = invokeDynamicConstant.getType(clazz);
225        String newDescriptor = newDescriptor(descriptor,
226                                             invokeDynamicConstant.referencedClasses);
227
228        if (!descriptor.equals(newDescriptor))
229        {
230            String name = invokeDynamicConstant.getName(clazz);
231
232            // Refer to a new NameAndType entry.
233            invokeDynamicConstant.u2nameAndTypeIndex =
234                new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(name, newDescriptor);
235        }
236    }
237
238
239    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
240    {
241        // Do we know the referenced class?
242        Clazz referencedClass = classConstant.referencedClass;
243        if (referencedClass != null)
244        {
245            // Has the class name changed?
246            String className    = classConstant.getName(clazz);
247            String newClassName = newClassName(className, referencedClass);
248            if (!className.equals(newClassName))
249            {
250                // Refer to a new Utf8 entry.
251                classConstant.u2nameIndex =
252                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName);
253            }
254        }
255    }
256
257
258    public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant)
259    {
260        // Has the descriptor changed?
261        String descriptor    = methodTypeConstant.getType(clazz);
262        String newDescriptor = newDescriptor(descriptor,
263                                             methodTypeConstant.referencedClasses);
264
265        if (!descriptor.equals(newDescriptor))
266        {
267            // Update the descriptor.
268            methodTypeConstant.u2descriptorIndex =
269                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor);
270        }
271    }
272
273
274    // Implementations for AttributeVisitor.
275
276    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
277
278
279    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
280    {
281        // Fix the inner class names.
282        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
283    }
284
285
286    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
287    {
288        // Fix the attributes.
289        codeAttribute.attributesAccept(clazz, method, this);
290    }
291
292
293    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
294    {
295        // Fix the types of the local variables.
296        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
297    }
298
299
300    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
301    {
302        // Fix the signatures of the local variables.
303        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
304    }
305
306
307    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
308    {
309        // Has the signature changed?
310        String signature    = signatureAttribute.getSignature(clazz);
311        String newSignature = newDescriptor(signature,
312                                            signatureAttribute.referencedClasses);
313
314        if (!signature.equals(newSignature))
315        {
316            // Update the signature.
317            signatureAttribute.u2signatureIndex =
318                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature);
319        }
320    }
321
322
323    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
324    {
325        // Fix the annotations.
326        annotationsAttribute.annotationsAccept(clazz, this);
327    }
328
329
330    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
331    {
332        // Fix the annotations.
333        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
334    }
335
336
337    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
338    {
339        // Fix the annotation.
340        annotationDefaultAttribute.defaultValueAccept(clazz, this);
341    }
342
343
344    // Implementations for InnerClassesInfoVisitor.
345
346    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
347    {
348        // Fix the inner class name.
349        int innerClassIndex = innerClassesInfo.u2innerClassIndex;
350        int innerNameIndex  = innerClassesInfo.u2innerNameIndex;
351        if (innerClassIndex != 0 &&
352            innerNameIndex  != 0)
353        {
354            String newInnerName = clazz.getClassName(innerClassIndex);
355            int index = newInnerName.lastIndexOf(ClassConstants.INNER_CLASS_SEPARATOR);
356            if (index >= 0)
357            {
358                innerClassesInfo.u2innerNameIndex =
359                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newInnerName.substring(index + 1));
360            }
361        }
362    }
363
364
365    // Implementations for LocalVariableInfoVisitor.
366
367    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
368    {
369        // Has the descriptor changed?
370        String descriptor    = localVariableInfo.getDescriptor(clazz);
371        String newDescriptor = newDescriptor(descriptor,
372                                             localVariableInfo.referencedClass);
373
374        if (!descriptor.equals(newDescriptor))
375        {
376            // Refer to a new Utf8 entry.
377            localVariableInfo.u2descriptorIndex =
378                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor);
379        }
380    }
381
382    // Implementations for LocalVariableTypeInfoVisitor.
383
384    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
385    {
386        // Has the signature changed?
387        String signature    = localVariableTypeInfo.getSignature(clazz);
388        String newSignature = newDescriptor(signature,
389                                            localVariableTypeInfo.referencedClasses);
390
391        if (!signature.equals(newSignature))
392        {
393            // Update the signature.
394            localVariableTypeInfo.u2signatureIndex =
395                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature);
396        }
397    }
398
399    // Implementations for AnnotationVisitor.
400
401    public void visitAnnotation(Clazz clazz, Annotation annotation)
402    {
403        // Has the type changed?
404        String typeName    = annotation.getType(clazz);
405        String newTypeName = newDescriptor(typeName,
406                                           annotation.referencedClasses);
407
408        if (!typeName.equals(newTypeName))
409        {
410            // Update the type.
411            annotation.u2typeIndex =
412                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName);
413        }
414
415        // Fix the element values.
416        annotation.elementValuesAccept(clazz, this);
417    }
418
419
420    // Implementations for ElementValueVisitor.
421
422    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
423    {
424    }
425
426
427    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
428    {
429        // Has the type name chamged?
430        String typeName    = enumConstantElementValue.getTypeName(clazz);
431        String newTypeName = newDescriptor(typeName,
432                                           enumConstantElementValue.referencedClasses);
433
434        if (!typeName.equals(newTypeName))
435        {
436            // Update the type name.
437            enumConstantElementValue.u2typeNameIndex =
438                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName);
439        }
440    }
441
442
443    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
444    {
445        // Has the class info changed?
446        String className    = classElementValue.getClassName(clazz);
447        String newClassName = newDescriptor(className,
448                                            classElementValue.referencedClasses);
449
450        if (!className.equals(newClassName))
451        {
452            // Update the class info.
453            classElementValue.u2classInfoIndex =
454                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName);
455        }
456    }
457
458
459    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
460    {
461        // Fix the annotation.
462        annotationElementValue.annotationAccept(clazz, this);
463    }
464
465
466    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
467    {
468        // Fix the element values.
469        arrayElementValue.elementValuesAccept(clazz, annotation, this);
470    }
471
472
473    // Small utility methods.
474
475    private static String newDescriptor(String descriptor,
476                                        Clazz  referencedClass)
477    {
478        // If there is no referenced class, the descriptor won't change.
479        if (referencedClass == null)
480        {
481            return descriptor;
482        }
483
484        // Unravel and reconstruct the class element of the descriptor.
485        DescriptorClassEnumeration descriptorClassEnumeration =
486            new DescriptorClassEnumeration(descriptor);
487
488        StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length());
489        newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff());
490
491        // Only if the descriptor contains a class name (e.g. with an array of
492        // primitive types), the descriptor can change.
493        if (descriptorClassEnumeration.hasMoreClassNames())
494        {
495            String className = descriptorClassEnumeration.nextClassName();
496            String fluff     = descriptorClassEnumeration.nextFluff();
497
498            String newClassName = newClassName(className,
499                                               referencedClass);
500
501            newDescriptorBuffer.append(newClassName);
502            newDescriptorBuffer.append(fluff);
503        }
504
505        return newDescriptorBuffer.toString();
506    }
507
508
509    private static String newDescriptor(String  descriptor,
510                                        Clazz[] referencedClasses)
511    {
512        // If there are no referenced classes, the descriptor won't change.
513        if (referencedClasses == null ||
514            referencedClasses.length == 0)
515        {
516            return descriptor;
517        }
518
519        // Unravel and reconstruct the class elements of the descriptor.
520        DescriptorClassEnumeration descriptorClassEnumeration =
521            new DescriptorClassEnumeration(descriptor);
522
523        StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length());
524        newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff());
525
526        int index = 0;
527        while (descriptorClassEnumeration.hasMoreClassNames())
528        {
529            String  className        = descriptorClassEnumeration.nextClassName();
530            boolean isInnerClassName = descriptorClassEnumeration.isInnerClassName();
531            String  fluff            = descriptorClassEnumeration.nextFluff();
532
533            String newClassName = newClassName(className,
534                                               referencedClasses[index++]);
535
536            // Strip the outer class name again, if it's an inner class.
537            if (isInnerClassName)
538            {
539                newClassName =
540                    newClassName.substring(newClassName.lastIndexOf(ClassConstants.INNER_CLASS_SEPARATOR)+1);
541            }
542
543            newDescriptorBuffer.append(newClassName);
544            newDescriptorBuffer.append(fluff);
545        }
546
547        return newDescriptorBuffer.toString();
548    }
549
550
551    /**
552     * Returns a unique class member name, based on the given name and descriptor.
553     */
554    private String newUniqueMemberName(String name, String descriptor)
555    {
556        return name.equals(ClassConstants.METHOD_NAME_INIT) ?
557            ClassConstants.METHOD_NAME_INIT :
558            name + ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode()));
559    }
560
561
562    /**
563     * Returns the new class name based on the given class name and the new
564     * name of the given referenced class. Class names of array types
565     * are handled properly.
566     */
567    private static String newClassName(String className,
568                                       Clazz  referencedClass)
569    {
570        // If there is no referenced class, the class name won't change.
571        if (referencedClass == null)
572        {
573            return className;
574        }
575
576        // Reconstruct the class name.
577        String newClassName = referencedClass.getName();
578
579        // Is it an array type?
580        if (className.charAt(0) == ClassConstants.TYPE_ARRAY)
581        {
582            // Add the array prefixes and suffix "[L...;".
583            newClassName =
584                 className.substring(0, className.indexOf(ClassConstants.TYPE_CLASS_START)+1) +
585                 newClassName +
586                 ClassConstants.TYPE_CLASS_END;
587        }
588
589        return newClassName;
590    }
591}
592