DynamicClassReferenceInitializer.java revision db267bc191f906f55eaef21a27110cce2ec57fdf
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.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(externalClassName);
305
306        stringConstant.referencedClass = findClass(clazz.getName(), internalClassName);
307    }
308
309
310    /**
311     * Prints out a note about the class cast to this class, if applicable.
312     */
313    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
314    {
315        // Print out a note about the class cast.
316        if (noteExceptionMatcher == null ||
317            !noteExceptionMatcher.matches(classConstant.getName(clazz)))
318        {
319            notePrinter.print(clazz.getName(),
320                              classConstant.getName(clazz),
321                              "Note: " +
322                              ClassUtil.externalClassName(clazz.getName()) +
323                              " calls '(" +
324                              ClassUtil.externalClassName(classConstant.getName(clazz)) +
325                              ")Class.forName(variable).newInstance()'");
326        }
327    }
328
329
330    /**
331     * Checks whether the referenced method is a .class method.
332     */
333    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
334    {
335        String methodType = methodrefConstant.getType(clazz);
336
337        // Do the method's class and type match?
338        if (methodType.equals(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC) ||
339            methodType.equals(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES))
340        {
341            String methodName = methodrefConstant.getName(clazz);
342
343            // Does the method's name match one of the special names?
344            isClassForNameInvocation =
345                methodName.equals(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JAVAC) ||
346                methodName.equals(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JIKES);
347
348            if (isClassForNameInvocation)
349            {
350                return;
351            }
352
353            String className  = methodrefConstant.getClassName(clazz);
354
355            // Note that we look for the class by name, since the referenced
356            // class has not been initialized yet.
357            Clazz referencedClass = programClassPool.getClass(className);
358            if (referencedClass != null)
359            {
360                // Check if the code of the referenced method is .class code.
361                // Note that we look for the method by name and type, since the
362                // referenced method has not been initialized yet.
363                referencedClass.methodAccept(methodName,
364                                             methodType,
365                                             new AllAttributeVisitor(this));
366            }
367        }
368    }
369
370
371    // Implementations for AttributeVisitor.
372
373    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
374
375
376    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
377    {
378        // Check whether this is class$(String), as generated by javac, or
379        // class(String, boolean), as generated by jikes, or an optimized
380        // version.
381        isClassForNameInvocation =
382            isDotClassMethodCode(clazz, method, codeAttribute,
383                                 dotClassJavacImplementationMatcher, 5)  ||
384            isDotClassMethodCode(clazz, method, codeAttribute,
385                                 dotClassJikesImplementationMatcher, 12) ||
386            isDotClassMethodCode(clazz, method, codeAttribute,
387                                 dotClassJikesImplementationMatcher2, 8);
388    }
389
390
391    // Small utility methods.
392
393    /**
394     * Returns whether the given method reference corresponds to a .class
395     * method, as generated by javac or by jikes.
396     */
397    private boolean isDotClassMethodref(Clazz clazz, int methodrefConstantIndex)
398    {
399        isClassForNameInvocation = false;
400
401        // Check if the code of the referenced method is .class code.
402        clazz.constantPoolEntryAccept(methodrefConstantIndex, this);
403
404        return isClassForNameInvocation;
405    }
406
407
408    /**
409     * Returns whether the first whether the first instructions of the
410     * given code attribute match with the given instruction matcher.
411     */
412    private boolean isDotClassMethodCode(Clazz                      clazz,
413                                         Method                     method,
414                                         CodeAttribute              codeAttribute,
415                                         InstructionSequenceMatcher codeMatcher,
416                                         int                        codeLength)
417    {
418        // Check the minimum code length.
419        if (codeAttribute.u4codeLength < codeLength)
420        {
421            return false;
422        }
423
424        // Check the actual instructions.
425        codeMatcher.reset();
426        codeAttribute.instructionsAccept(clazz, method, 0, codeLength, codeMatcher);
427        return codeMatcher.isMatching();
428    }
429
430
431    /**
432     * Returns the class with the given name, either for the program class pool
433     * or from the library class pool, or <code>null</code> if it can't be found.
434     */
435    private Clazz findClass(String referencingClassName, String name)
436    {
437        // Ignore any primitive array types.
438        if (ClassUtil.isInternalArrayType(name) &&
439            !ClassUtil.isInternalClassType(name))
440        {
441            return null;
442        }
443
444        // First look for the class in the program class pool.
445        Clazz clazz = programClassPool.getClass(name);
446
447        // Otherwise look for the class in the library class pool.
448        if (clazz == null)
449        {
450            clazz = libraryClassPool.getClass(name);
451
452            if (clazz == null &&
453                missingNotePrinter != null)
454            {
455                // We didn't find the superclass or interface. Print a note.
456                missingNotePrinter.print(referencingClassName,
457                                         name,
458                                         "Note: " +
459                                         ClassUtil.externalClassName(referencingClassName) +
460                                         ": can't find dynamically referenced class " +
461                                         ClassUtil.externalClassName(name));
462            }
463        }
464        else if (dependencyWarningPrinter != null)
465        {
466            // The superclass or interface was found in the program class pool.
467            // Print a warning.
468            dependencyWarningPrinter.print(referencingClassName,
469                                           name,
470                                           "Warning: library class " +
471                                           ClassUtil.externalClassName(referencingClassName) +
472                                           " depends dynamically on program class " +
473                                           ClassUtil.externalClassName(name));
474        }
475
476        return clazz;
477    }
478}
479