| /* |
| * ProGuard -- shrinking, optimization, obfuscation, and preverification |
| * of Java bytecode. |
| * |
| * Copyright (c) 2002-2014 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.NAME_JAVA_LANG_CLASS), |
| new Utf8Constant(ClassConstants.METHOD_NAME_CLASS_FOR_NAME), |
| new Utf8Constant(ClassConstants.METHOD_TYPE_CLASS_FOR_NAME), |
| |
| // 6 |
| new MethodrefConstant(1, 7, null, null), |
| new NameAndTypeConstant(8, 9), |
| new Utf8Constant(ClassConstants.METHOD_NAME_NEW_INSTANCE), |
| new Utf8Constant(ClassConstants.METHOD_TYPE_NEW_INSTANCE), |
| |
| // 10 |
| new MethodrefConstant(1, 11, null, null), |
| new NameAndTypeConstant(12, 13), |
| new Utf8Constant(ClassConstants.METHOD_NAME_CLASS_GET_COMPONENT_TYPE), |
| new Utf8Constant(ClassConstants.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.METHOD_NAME_DOT_CLASS_JAVAC), |
| // new Utf8Constant(ClassConstants.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.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.METHOD_NAME_DOT_CLASS_JIKES), |
| // new Utf8Constant(ClassConstants.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.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 (SomeClass)Class.forName(someName).newInstance() |
| // construct. Apply this matcher first, so the next matcher can still |
| // reset it after the first instruction. |
| 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 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 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( |
| ClassUtil.externalBaseType(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.METHOD_TYPE_DOT_CLASS_JAVAC) || |
| methodType.equals(ClassConstants.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.METHOD_NAME_DOT_CLASS_JAVAC) || |
| methodName.equals(ClassConstants.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) |
| { |
| // Is it an array type? |
| if (ClassUtil.isInternalArrayType(name)) |
| { |
| // Ignore any primitive array types. |
| if (!ClassUtil.isInternalClassType(name)) |
| { |
| return null; |
| } |
| |
| // Strip the array part. |
| name = ClassUtil.internalClassNameFromClassType(name); |
| } |
| |
| // 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; |
| } |
| } |