/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2010 Eric Lafortune (eric@graphics.cornell.edu) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.classfile.util; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.util.StringMatcher; /** * This InstructionVisitor initializes any constant <code>Class.forName</code> or * <code>.class</code> references of all classes it visits. More specifically, * it fills out the references of string constant pool entries that refer to a * class in the program class pool or in the library class pool. * <p> * It optionally prints notes if on usage of * <code>(SomeClass)Class.forName(variable).newInstance()</code>. * <p> * The class hierarchy must be initialized before using this visitor. * * @see ClassSuperHierarchyInitializer * * @author Eric Lafortune */ public class DynamicClassReferenceInitializer extends SimplifiedVisitor implements InstructionVisitor, ConstantVisitor, AttributeVisitor { public static final int X = InstructionSequenceMatcher.X; public static final int Y = InstructionSequenceMatcher.Y; public static final int Z = InstructionSequenceMatcher.Z; public static final int A = InstructionSequenceMatcher.A; public static final int B = InstructionSequenceMatcher.B; public static final int C = InstructionSequenceMatcher.C; public static final int D = InstructionSequenceMatcher.D; private final Constant[] CLASS_FOR_NAME_CONSTANTS = new Constant[] { // 0 new MethodrefConstant(1, 2, null, null), new ClassConstant(3, null), new NameAndTypeConstant(4, 5), new Utf8Constant(ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS), new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_FOR_NAME), new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_FOR_NAME), // 6 new MethodrefConstant(1, 7, null, null), new NameAndTypeConstant(8, 9), new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_NEW_INSTANCE), new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_NEW_INSTANCE), // 10 new MethodrefConstant(1, 11, null, null), new NameAndTypeConstant(12, 13), new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_GET_COMPONENT_TYPE), new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_GET_COMPONENT_TYPE), }; // Class.forName("SomeClass"). private final Instruction[] CONSTANT_CLASS_FOR_NAME_INSTRUCTIONS = new Instruction[] { new ConstantInstruction(InstructionConstants.OP_LDC, X), new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0), }; // (SomeClass)Class.forName(someName).newInstance(). private final Instruction[] CLASS_FOR_NAME_CAST_INSTRUCTIONS = new Instruction[] { new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0), new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 6), new ConstantInstruction(InstructionConstants.OP_CHECKCAST, X), }; // private Constant[] DOT_CLASS_JAVAC_CONSTANTS = new Constant[] // { // new MethodrefConstant(A, 1, null, null), // new NameAndTypeConstant(2, 3), // new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JAVAC), // new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC), // }; private final Constant[] DOT_CLASS_JAVAC_CONSTANTS = new Constant[] { new MethodrefConstant(A, 1, null, null), new NameAndTypeConstant(B, 2), new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC), }; // SomeClass.class = class$("SomeClass") (javac). private final Instruction[] DOT_CLASS_JAVAC_INSTRUCTIONS = new Instruction[] { new ConstantInstruction(InstructionConstants.OP_LDC, X), new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0), }; // private Constant[] DOT_CLASS_JIKES_CONSTANTS = new Constant[] // { // new MethodrefConstant(A, 1, null, null), // new NameAndTypeConstant(2, 3), // new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JIKES), // new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES), // }; private final Constant[] DOT_CLASS_JIKES_CONSTANTS = new Constant[] { new MethodrefConstant(A, 1, null, null), new NameAndTypeConstant(B, 2), new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES), }; // SomeClass.class = class("SomeClass", false) (jikes). private final Instruction[] DOT_CLASS_JIKES_INSTRUCTIONS = new Instruction[] { new ConstantInstruction(InstructionConstants.OP_LDC, X), new SimpleInstruction(InstructionConstants.OP_ICONST_0), new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0), }; // return Class.forName(v0). private final Instruction[] DOT_CLASS_JAVAC_IMPLEMENTATION_INSTRUCTIONS = new Instruction[] { new VariableInstruction(InstructionConstants.OP_ALOAD_0), new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0), new SimpleInstruction(InstructionConstants.OP_ARETURN), }; // return Class.forName(v0), if (!v1) .getComponentType(). private final Instruction[] DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS = new Instruction[] { new VariableInstruction(InstructionConstants.OP_ALOAD_0), new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0), new VariableInstruction(InstructionConstants.OP_ALOAD_1), new BranchInstruction(InstructionConstants.OP_IFNE, +6), new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 10), new SimpleInstruction(InstructionConstants.OP_ARETURN), }; // return Class.forName(v0).getComponentType(). private final Instruction[] DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS2 = new Instruction[] { new VariableInstruction(InstructionConstants.OP_ALOAD_0), new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0), new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 10), new SimpleInstruction(InstructionConstants.OP_ARETURN), }; private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final WarningPrinter missingNotePrinter; private final WarningPrinter dependencyWarningPrinter; private final WarningPrinter notePrinter; private final StringMatcher noteExceptionMatcher; private final InstructionSequenceMatcher constantClassForNameMatcher = new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS, CONSTANT_CLASS_FOR_NAME_INSTRUCTIONS); private final InstructionSequenceMatcher classForNameCastMatcher = new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS, CLASS_FOR_NAME_CAST_INSTRUCTIONS); private final InstructionSequenceMatcher dotClassJavacMatcher = new InstructionSequenceMatcher(DOT_CLASS_JAVAC_CONSTANTS, DOT_CLASS_JAVAC_INSTRUCTIONS); private final InstructionSequenceMatcher dotClassJikesMatcher = new InstructionSequenceMatcher(DOT_CLASS_JIKES_CONSTANTS, DOT_CLASS_JIKES_INSTRUCTIONS); private final InstructionSequenceMatcher dotClassJavacImplementationMatcher = new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS, DOT_CLASS_JAVAC_IMPLEMENTATION_INSTRUCTIONS); private final InstructionSequenceMatcher dotClassJikesImplementationMatcher = new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS, DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS); private final InstructionSequenceMatcher dotClassJikesImplementationMatcher2 = new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS, DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS2); // A field acting as a return variable for the visitors. private boolean isClassForNameInvocation; /** * Creates a new DynamicClassReferenceInitializer that optionally prints * warnings and notes, with optional class specifications for which never * to print notes. */ public DynamicClassReferenceInitializer(ClassPool programClassPool, ClassPool libraryClassPool, WarningPrinter missingNotePrinter, WarningPrinter dependencyWarningPrinter, WarningPrinter notePrinter, StringMatcher noteExceptionMatcher) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.missingNotePrinter = missingNotePrinter; this.dependencyWarningPrinter = dependencyWarningPrinter; this.notePrinter = notePrinter; this.noteExceptionMatcher = noteExceptionMatcher; } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { // Try to match the Class.forName("SomeClass") construct. instruction.accept(clazz, method, codeAttribute, offset, constantClassForNameMatcher); // Did we find a match? if (constantClassForNameMatcher.isMatching()) { // Fill out the matched string constant. clazz.constantPoolEntryAccept(constantClassForNameMatcher.matchedConstantIndex(X), this); // Don't look for the dynamic construct. classForNameCastMatcher.reset(); } // Try to match the (SomeClass)Class.forName(someName).newInstance() // construct. instruction.accept(clazz, method, codeAttribute, offset, classForNameCastMatcher); // Did we find a match? if (classForNameCastMatcher.isMatching()) { // Print out a note about the construct. clazz.constantPoolEntryAccept(classForNameCastMatcher.matchedConstantIndex(X), this); } // Try to match the javac .class construct. instruction.accept(clazz, method, codeAttribute, offset, dotClassJavacMatcher); // Did we find a match? if (dotClassJavacMatcher.isMatching() && isDotClassMethodref(clazz, dotClassJavacMatcher.matchedConstantIndex(0))) { // Fill out the matched string constant. clazz.constantPoolEntryAccept(dotClassJavacMatcher.matchedConstantIndex(X), this); } // Try to match the jikes .class construct. instruction.accept(clazz, method, codeAttribute, offset, dotClassJikesMatcher); // Did we find a match? if (dotClassJikesMatcher.isMatching() && isDotClassMethodref(clazz, dotClassJikesMatcher.matchedConstantIndex(0))) { // Fill out the matched string constant. clazz.constantPoolEntryAccept(dotClassJikesMatcher.matchedConstantIndex(X), this); } } // Implementations for ConstantVisitor. /** * Fills out the link to the referenced class. */ public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Save a reference to the corresponding class. String externalClassName = stringConstant.getString(clazz); String internalClassName = ClassUtil.internalClassName(externalClassName); stringConstant.referencedClass = findClass(clazz.getName(), internalClassName); } /** * Prints out a note about the class cast to this class, if applicable. */ public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Print out a note about the class cast. if (noteExceptionMatcher == null || !noteExceptionMatcher.matches(classConstant.getName(clazz))) { notePrinter.print(clazz.getName(), classConstant.getName(clazz), "Note: " + ClassUtil.externalClassName(clazz.getName()) + " calls '(" + ClassUtil.externalClassName(classConstant.getName(clazz)) + ")Class.forName(variable).newInstance()'"); } } /** * Checks whether the referenced method is a .class method. */ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { String methodType = methodrefConstant.getType(clazz); // Do the method's class and type match? if (methodType.equals(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC) || methodType.equals(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES)) { String methodName = methodrefConstant.getName(clazz); // Does the method's name match one of the special names? isClassForNameInvocation = methodName.equals(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JAVAC) || methodName.equals(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JIKES); if (isClassForNameInvocation) { return; } String className = methodrefConstant.getClassName(clazz); // Note that we look for the class by name, since the referenced // class has not been initialized yet. Clazz referencedClass = programClassPool.getClass(className); if (referencedClass != null) { // Check if the code of the referenced method is .class code. // Note that we look for the method by name and type, since the // referenced method has not been initialized yet. referencedClass.methodAccept(methodName, methodType, new AllAttributeVisitor(this)); } } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Check whether this is class$(String), as generated by javac, or // class(String, boolean), as generated by jikes, or an optimized // version. isClassForNameInvocation = isDotClassMethodCode(clazz, method, codeAttribute, dotClassJavacImplementationMatcher, 5) || isDotClassMethodCode(clazz, method, codeAttribute, dotClassJikesImplementationMatcher, 12) || isDotClassMethodCode(clazz, method, codeAttribute, dotClassJikesImplementationMatcher2, 8); } // Small utility methods. /** * Returns whether the given method reference corresponds to a .class * method, as generated by javac or by jikes. */ private boolean isDotClassMethodref(Clazz clazz, int methodrefConstantIndex) { isClassForNameInvocation = false; // Check if the code of the referenced method is .class code. clazz.constantPoolEntryAccept(methodrefConstantIndex, this); return isClassForNameInvocation; } /** * Returns whether the first whether the first instructions of the * given code attribute match with the given instruction matcher. */ private boolean isDotClassMethodCode(Clazz clazz, Method method, CodeAttribute codeAttribute, InstructionSequenceMatcher codeMatcher, int codeLength) { // Check the minimum code length. if (codeAttribute.u4codeLength < codeLength) { return false; } // Check the actual instructions. codeMatcher.reset(); codeAttribute.instructionsAccept(clazz, method, 0, codeLength, codeMatcher); return codeMatcher.isMatching(); } /** * Returns the class with the given name, either for the program class pool * or from the library class pool, or <code>null</code> if it can't be found. */ private Clazz findClass(String referencingClassName, String name) { // Ignore any primitive array types. if (ClassUtil.isInternalArrayType(name) && !ClassUtil.isInternalClassType(name)) { return null; } // First look for the class in the program class pool. Clazz clazz = programClassPool.getClass(name); // Otherwise look for the class in the library class pool. if (clazz == null) { clazz = libraryClassPool.getClass(name); if (clazz == null && missingNotePrinter != null) { // We didn't find the superclass or interface. Print a note. missingNotePrinter.print(referencingClassName, name, "Note: " + ClassUtil.externalClassName(referencingClassName) + ": can't find dynamically referenced class " + ClassUtil.externalClassName(name)); } } else if (dependencyWarningPrinter != null) { // The superclass or interface was found in the program class pool. // Print a warning. dependencyWarningPrinter.print(referencingClassName, name, "Warning: library class " + ClassUtil.externalClassName(referencingClassName) + " depends dynamically on program class " + ClassUtil.externalClassName(name)); } return clazz; } }