DynamicClassReferenceInitializer.java revision cfead78069f3dc32998dc118ee08cab3867acea2
1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2011 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.util;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.*;
25import proguard.classfile.attribute.visitor.*;
26import proguard.classfile.constant.*;
27import proguard.classfile.constant.visitor.ConstantVisitor;
28import proguard.classfile.instruction.*;
29import proguard.classfile.instruction.visitor.InstructionVisitor;
30import proguard.util.StringMatcher;
31
32/**
33 * This InstructionVisitor initializes any constant <code>Class.forName</code> or
34 * <code>.class</code> references of all classes it visits. More specifically,
35 * it fills out the references of string constant pool entries that refer to a
36 * class in the program class pool or in the library class pool.
37 * <p>
38 * It optionally prints notes if on usage of
39 * <code>(SomeClass)Class.forName(variable).newInstance()</code>.
40 * <p>
41 * The class hierarchy must be initialized before using this visitor.
42 *
43 * @see ClassSuperHierarchyInitializer
44 *
45 * @author Eric Lafortune
46 */
47public class DynamicClassReferenceInitializer
48extends      SimplifiedVisitor
49implements   InstructionVisitor,
50             ConstantVisitor,
51             AttributeVisitor
52{
53    public static final int X = InstructionSequenceMatcher.X;
54    public static final int Y = InstructionSequenceMatcher.Y;
55    public static final int Z = InstructionSequenceMatcher.Z;
56
57    public static final int A = InstructionSequenceMatcher.A;
58    public static final int B = InstructionSequenceMatcher.B;
59    public static final int C = InstructionSequenceMatcher.C;
60    public static final int D = InstructionSequenceMatcher.D;
61
62
63    private final Constant[] CLASS_FOR_NAME_CONSTANTS = new Constant[]
64    {
65        // 0
66        new MethodrefConstant(1, 2, null, null),
67        new ClassConstant(3, null),
68        new NameAndTypeConstant(4, 5),
69        new Utf8Constant(ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS),
70        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_FOR_NAME),
71        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_FOR_NAME),
72
73        // 6
74        new MethodrefConstant(1, 7, null, null),
75        new NameAndTypeConstant(8, 9),
76        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_NEW_INSTANCE),
77        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_NEW_INSTANCE),
78
79        // 10
80        new MethodrefConstant(1, 11, null, null),
81        new NameAndTypeConstant(12, 13),
82        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_GET_COMPONENT_TYPE),
83        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_GET_COMPONENT_TYPE),
84    };
85
86    // Class.forName("SomeClass").
87    private final Instruction[] CONSTANT_CLASS_FOR_NAME_INSTRUCTIONS = new Instruction[]
88    {
89        new ConstantInstruction(InstructionConstants.OP_LDC, X),
90        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
91    };
92
93    // (SomeClass)Class.forName(someName).newInstance().
94    private final Instruction[] CLASS_FOR_NAME_CAST_INSTRUCTIONS = new Instruction[]
95    {
96        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
97        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 6),
98        new ConstantInstruction(InstructionConstants.OP_CHECKCAST, X),
99    };
100
101
102//    private Constant[] DOT_CLASS_JAVAC_CONSTANTS = new Constant[]
103//    {
104//        new MethodrefConstant(A, 1, null, null),
105//        new NameAndTypeConstant(2, 3),
106//        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JAVAC),
107//        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC),
108//    };
109
110    private final Constant[] DOT_CLASS_JAVAC_CONSTANTS = new Constant[]
111    {
112        new MethodrefConstant(A, 1, null, null),
113        new NameAndTypeConstant(B, 2),
114        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC),
115    };
116
117    // SomeClass.class = class$("SomeClass") (javac).
118    private final Instruction[] DOT_CLASS_JAVAC_INSTRUCTIONS = new Instruction[]
119    {
120        new ConstantInstruction(InstructionConstants.OP_LDC, X),
121        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
122    };
123
124
125//    private Constant[] DOT_CLASS_JIKES_CONSTANTS = new Constant[]
126//    {
127//        new MethodrefConstant(A, 1, null, null),
128//        new NameAndTypeConstant(2, 3),
129//        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JIKES),
130//        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES),
131//    };
132
133    private final Constant[] DOT_CLASS_JIKES_CONSTANTS = new Constant[]
134    {
135        new MethodrefConstant(A, 1, null, null),
136        new NameAndTypeConstant(B, 2),
137        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES),
138    };
139
140    // SomeClass.class = class("SomeClass", false) (jikes).
141    private final Instruction[] DOT_CLASS_JIKES_INSTRUCTIONS = new Instruction[]
142    {
143        new ConstantInstruction(InstructionConstants.OP_LDC, X),
144        new SimpleInstruction(InstructionConstants.OP_ICONST_0),
145        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
146    };
147
148    // return Class.forName(v0).
149    private final Instruction[] DOT_CLASS_JAVAC_IMPLEMENTATION_INSTRUCTIONS = new Instruction[]
150    {
151        new VariableInstruction(InstructionConstants.OP_ALOAD_0),
152        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
153        new SimpleInstruction(InstructionConstants.OP_ARETURN),
154    };
155
156    // return Class.forName(v0), if (!v1) .getComponentType().
157    private final Instruction[] DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS = new Instruction[]
158    {
159        new VariableInstruction(InstructionConstants.OP_ALOAD_0),
160        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
161        new VariableInstruction(InstructionConstants.OP_ALOAD_1),
162        new BranchInstruction(InstructionConstants.OP_IFNE, +6),
163        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 10),
164        new SimpleInstruction(InstructionConstants.OP_ARETURN),
165    };
166
167    // return Class.forName(v0).getComponentType().
168    private final Instruction[] DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS2 = new Instruction[]
169    {
170        new VariableInstruction(InstructionConstants.OP_ALOAD_0),
171        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
172        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 10),
173        new SimpleInstruction(InstructionConstants.OP_ARETURN),
174    };
175
176
177    private final ClassPool      programClassPool;
178    private final ClassPool      libraryClassPool;
179    private final WarningPrinter missingNotePrinter;
180    private final WarningPrinter dependencyWarningPrinter;
181    private final WarningPrinter notePrinter;
182    private final StringMatcher  noteExceptionMatcher;
183
184
185    private final InstructionSequenceMatcher constantClassForNameMatcher =
186        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
187                                       CONSTANT_CLASS_FOR_NAME_INSTRUCTIONS);
188
189    private final InstructionSequenceMatcher classForNameCastMatcher =
190        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
191                                       CLASS_FOR_NAME_CAST_INSTRUCTIONS);
192
193    private final InstructionSequenceMatcher dotClassJavacMatcher =
194        new InstructionSequenceMatcher(DOT_CLASS_JAVAC_CONSTANTS,
195                                       DOT_CLASS_JAVAC_INSTRUCTIONS);
196
197    private final InstructionSequenceMatcher dotClassJikesMatcher =
198        new InstructionSequenceMatcher(DOT_CLASS_JIKES_CONSTANTS,
199                                       DOT_CLASS_JIKES_INSTRUCTIONS);
200
201    private final InstructionSequenceMatcher dotClassJavacImplementationMatcher =
202        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
203                                       DOT_CLASS_JAVAC_IMPLEMENTATION_INSTRUCTIONS);
204
205    private final InstructionSequenceMatcher dotClassJikesImplementationMatcher =
206        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
207                                       DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS);
208
209    private final InstructionSequenceMatcher dotClassJikesImplementationMatcher2 =
210        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
211                                       DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS2);
212
213
214    // A field acting as a return variable for the visitors.
215    private boolean isClassForNameInvocation;
216
217
218    /**
219     * Creates a new DynamicClassReferenceInitializer that optionally prints
220     * warnings and notes, with optional class specifications for which never
221     * to print notes.
222     */
223    public DynamicClassReferenceInitializer(ClassPool      programClassPool,
224                                            ClassPool      libraryClassPool,
225                                            WarningPrinter missingNotePrinter,
226                                            WarningPrinter dependencyWarningPrinter,
227                                            WarningPrinter notePrinter,
228                                            StringMatcher  noteExceptionMatcher)
229    {
230        this.programClassPool         = programClassPool;
231        this.libraryClassPool         = libraryClassPool;
232        this.missingNotePrinter       = missingNotePrinter;
233        this.dependencyWarningPrinter = dependencyWarningPrinter;
234        this.notePrinter              = notePrinter;
235        this.noteExceptionMatcher     = noteExceptionMatcher;
236    }
237
238
239    // Implementations for InstructionVisitor.
240
241    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
242    {
243        // Try to match the Class.forName("SomeClass") construct.
244        instruction.accept(clazz, method, codeAttribute, offset,
245                           constantClassForNameMatcher);
246
247        // Did we find a match?
248        if (constantClassForNameMatcher.isMatching())
249        {
250            // Fill out the matched string constant.
251            clazz.constantPoolEntryAccept(constantClassForNameMatcher.matchedConstantIndex(X), this);
252
253            // Don't look for the dynamic construct.
254            classForNameCastMatcher.reset();
255        }
256
257        // Try to match the (SomeClass)Class.forName(someName).newInstance()
258        // construct.
259        instruction.accept(clazz, method, codeAttribute, offset,
260                           classForNameCastMatcher);
261
262        // Did we find a match?
263        if (classForNameCastMatcher.isMatching())
264        {
265            // Print out a note about the construct.
266            clazz.constantPoolEntryAccept(classForNameCastMatcher.matchedConstantIndex(X), this);
267        }
268
269        // Try to match the javac .class construct.
270        instruction.accept(clazz, method, codeAttribute, offset,
271                           dotClassJavacMatcher);
272
273        // Did we find a match?
274        if (dotClassJavacMatcher.isMatching() &&
275            isDotClassMethodref(clazz, dotClassJavacMatcher.matchedConstantIndex(0)))
276        {
277            // Fill out the matched string constant.
278            clazz.constantPoolEntryAccept(dotClassJavacMatcher.matchedConstantIndex(X), this);
279        }
280
281        // Try to match the jikes .class construct.
282        instruction.accept(clazz, method, codeAttribute, offset,
283                           dotClassJikesMatcher);
284
285        // Did we find a match?
286        if (dotClassJikesMatcher.isMatching() &&
287            isDotClassMethodref(clazz, dotClassJikesMatcher.matchedConstantIndex(0)))
288        {
289            // Fill out the matched string constant.
290            clazz.constantPoolEntryAccept(dotClassJikesMatcher.matchedConstantIndex(X), this);
291        }
292    }
293
294
295    // Implementations for ConstantVisitor.
296
297    /**
298     * Fills out the link to the referenced class.
299     */
300    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
301    {
302        // Save a reference to the corresponding class.
303        String externalClassName = stringConstant.getString(clazz);
304        String internalClassName = ClassUtil.internalClassName(
305                                   ClassUtil.externalBaseType(externalClassName));
306
307        stringConstant.referencedClass = findClass(clazz.getName(), internalClassName);
308    }
309
310
311    /**
312     * Prints out a note about the class cast to this class, if applicable.
313     */
314    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
315    {
316        // Print out a note about the class cast.
317        if (noteExceptionMatcher == null ||
318            !noteExceptionMatcher.matches(classConstant.getName(clazz)))
319        {
320            notePrinter.print(clazz.getName(),
321                              classConstant.getName(clazz),
322                              "Note: " +
323                              ClassUtil.externalClassName(clazz.getName()) +
324                              " calls '(" +
325                              ClassUtil.externalClassName(classConstant.getName(clazz)) +
326                              ")Class.forName(variable).newInstance()'");
327        }
328    }
329
330
331    /**
332     * Checks whether the referenced method is a .class method.
333     */
334    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
335    {
336        String methodType = methodrefConstant.getType(clazz);
337
338        // Do the method's class and type match?
339        if (methodType.equals(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC) ||
340            methodType.equals(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES))
341        {
342            String methodName = methodrefConstant.getName(clazz);
343
344            // Does the method's name match one of the special names?
345            isClassForNameInvocation =
346                methodName.equals(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JAVAC) ||
347                methodName.equals(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JIKES);
348
349            if (isClassForNameInvocation)
350            {
351                return;
352            }
353
354            String className = methodrefConstant.getClassName(clazz);
355
356            // Note that we look for the class by name, since the referenced
357            // class has not been initialized yet.
358            Clazz referencedClass = programClassPool.getClass(className);
359            if (referencedClass != null)
360            {
361                // Check if the code of the referenced method is .class code.
362                // Note that we look for the method by name and type, since the
363                // referenced method has not been initialized yet.
364                referencedClass.methodAccept(methodName,
365                                             methodType,
366                                             new AllAttributeVisitor(this));
367            }
368        }
369    }
370
371
372    // Implementations for AttributeVisitor.
373
374    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
375
376
377    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
378    {
379        // Check whether this is class$(String), as generated by javac, or
380        // class(String, boolean), as generated by jikes, or an optimized
381        // version.
382        isClassForNameInvocation =
383            isDotClassMethodCode(clazz, method, codeAttribute,
384                                 dotClassJavacImplementationMatcher, 5)  ||
385            isDotClassMethodCode(clazz, method, codeAttribute,
386                                 dotClassJikesImplementationMatcher, 12) ||
387            isDotClassMethodCode(clazz, method, codeAttribute,
388                                 dotClassJikesImplementationMatcher2, 8);
389    }
390
391
392    // Small utility methods.
393
394    /**
395     * Returns whether the given method reference corresponds to a .class
396     * method, as generated by javac or by jikes.
397     */
398    private boolean isDotClassMethodref(Clazz clazz, int methodrefConstantIndex)
399    {
400        isClassForNameInvocation = false;
401
402        // Check if the code of the referenced method is .class code.
403        clazz.constantPoolEntryAccept(methodrefConstantIndex, this);
404
405        return isClassForNameInvocation;
406    }
407
408
409    /**
410     * Returns whether the first whether the first instructions of the
411     * given code attribute match with the given instruction matcher.
412     */
413    private boolean isDotClassMethodCode(Clazz                      clazz,
414                                         Method                     method,
415                                         CodeAttribute              codeAttribute,
416                                         InstructionSequenceMatcher codeMatcher,
417                                         int                        codeLength)
418    {
419        // Check the minimum code length.
420        if (codeAttribute.u4codeLength < codeLength)
421        {
422            return false;
423        }
424
425        // Check the actual instructions.
426        codeMatcher.reset();
427        codeAttribute.instructionsAccept(clazz, method, 0, codeLength, codeMatcher);
428        return codeMatcher.isMatching();
429    }
430
431
432    /**
433     * Returns the class with the given name, either for the program class pool
434     * or from the library class pool, or <code>null</code> if it can't be found.
435     */
436    private Clazz findClass(String referencingClassName, String name)
437    {
438        // Is it an array type?
439        if (ClassUtil.isInternalArrayType(name))
440        {
441            // Ignore any primitive array types.
442            if (!ClassUtil.isInternalClassType(name))
443            {
444                return null;
445            }
446
447            // Strip the array part.
448            name = ClassUtil.internalClassNameFromClassType(name);
449        }
450
451        // First look for the class in the program class pool.
452        Clazz clazz = programClassPool.getClass(name);
453
454        // Otherwise look for the class in the library class pool.
455        if (clazz == null)
456        {
457            clazz = libraryClassPool.getClass(name);
458
459            if (clazz == null &&
460                missingNotePrinter != null)
461            {
462                // We didn't find the superclass or interface. Print a note.
463                missingNotePrinter.print(referencingClassName,
464                                         name,
465                                         "Note: " +
466                                         ClassUtil.externalClassName(referencingClassName) +
467                                         ": can't find dynamically referenced class " +
468                                         ClassUtil.externalClassName(name));
469            }
470        }
471        else if (dependencyWarningPrinter != null)
472        {
473            // The superclass or interface was found in the program class pool.
474            // Print a warning.
475            dependencyWarningPrinter.print(referencingClassName,
476                                           name,
477                                           "Warning: library class " +
478                                           ClassUtil.externalClassName(referencingClassName) +
479                                           " depends dynamically on program class " +
480                                           ClassUtil.externalClassName(name));
481        }
482
483        return clazz;
484    }
485}
486