All Downloads are FREE. Search and download functionalities are using the official Maven repository.

proguard.classfile.util.DynamicClassReferenceInitializer Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.6
Show newest version
/*
 * ProGuardCORE -- library to process Java bytecode.
 *
 * Copyright (c) 2002-2023 Guardsquare NV
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package proguard.classfile.util;

import static proguard.classfile.ClassConstants.NAME_JAVA_LANG_CLASS;

import proguard.classfile.ClassConstants;
import proguard.classfile.ClassPool;
import proguard.classfile.Clazz;
import proguard.classfile.Method;
import proguard.classfile.ProgramClass;
import proguard.classfile.attribute.Attribute;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
import proguard.classfile.attribute.visitor.AttributeNameFilter;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.constant.ClassConstant;
import proguard.classfile.constant.Constant;
import proguard.classfile.constant.MethodrefConstant;
import proguard.classfile.constant.NameAndTypeConstant;
import proguard.classfile.constant.StringConstant;
import proguard.classfile.constant.Utf8Constant;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.instruction.BranchInstruction;
import proguard.classfile.instruction.ConstantInstruction;
import proguard.classfile.instruction.Instruction;
import proguard.classfile.instruction.SimpleInstruction;
import proguard.classfile.instruction.VariableInstruction;
import proguard.classfile.instruction.visitor.AllInstructionVisitor;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.instruction.visitor.MultiInstructionVisitor;
import proguard.classfile.visitor.AllMethodVisitor;
import proguard.classfile.visitor.ClassConstantClassFilter;
import proguard.classfile.visitor.ClassVisitor;
import proguard.util.FixedStringMatcher;
import proguard.util.StringMatcher;

/**
 * This {@link InstructionVisitor} initializes any constant Class.forName or 
 * .class 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.
 *
 * 

It optionally prints notes if on usage of * (SomeClass)Class.forName(variable).newInstance(). * *

The class hierarchy must be initialized before using this visitor. * *

It's more efficient to use as a {@link ClassVisitor} than an {@link InstructionVisitor}. * * @see ClassSuperHierarchyInitializer * @author Eric Lafortune */ public class DynamicClassReferenceInitializer implements ClassVisitor, 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), // 14 getclassloader new MethodrefConstant(1, 15, null, null), new NameAndTypeConstant(16, 17), new Utf8Constant(ClassConstants.METHOD_NAME_CLASS_GET_CLASS_LOADER), new Utf8Constant(ClassConstants.METHOD_TYPE_CLASS_GET_CLASS_LOADER), // 18 3-arg forName new MethodrefConstant(1, 19, null, null), new NameAndTypeConstant(4, 20), new Utf8Constant(ClassConstants.METHOD_TYPE_CLASS_FOR_NAME_CLASSLOADER) }; // Class.forName("SomeClass"). private final Instruction[] CONSTANT_CLASS_FOR_NAME_INSTRUCTIONS = new Instruction[] { new ConstantInstruction(Instruction.OP_LDC, X), new ConstantInstruction(Instruction.OP_INVOKESTATIC, 0), }; // Class.forName("SomeClass", true/false, class.getClassLoader()). private final Instruction[] CONSTANT_CLASS_FOR_NAME_3_INSTRUCTIONS = new Instruction[] { new ConstantInstruction(Instruction.OP_LDC, X), new SimpleInstruction(Instruction.OP_ICONST_0, Y), new ConstantInstruction(Instruction.OP_LDC, Z), new ConstantInstruction(Instruction.OP_INVOKEVIRTUAL, 14), new ConstantInstruction(Instruction.OP_INVOKESTATIC, 18), }; // (SomeClass)Class.forName(someName).newInstance(). private final Instruction[] CLASS_FOR_NAME_CAST_INSTRUCTIONS = new Instruction[] { new ConstantInstruction(Instruction.OP_INVOKESTATIC, 0), new ConstantInstruction(Instruction.OP_INVOKEVIRTUAL, 6), new ConstantInstruction(Instruction.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(Instruction.OP_LDC, X), new ConstantInstruction(Instruction.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(Instruction.OP_LDC, X), new SimpleInstruction(Instruction.OP_ICONST_0), new ConstantInstruction(Instruction.OP_INVOKESTATIC, 0), }; // return Class.forName(v0). private final Instruction[] DOT_CLASS_JAVAC_IMPLEMENTATION_INSTRUCTIONS = new Instruction[] { new VariableInstruction(Instruction.OP_ALOAD_0), new ConstantInstruction(Instruction.OP_INVOKESTATIC, 0), new SimpleInstruction(Instruction.OP_ARETURN), }; // return Class.forName(v0), if (!v1) .getComponentType(). private final Instruction[] DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS = new Instruction[] { new VariableInstruction(Instruction.OP_ALOAD_0), new ConstantInstruction(Instruction.OP_INVOKESTATIC, 0), new VariableInstruction(Instruction.OP_ALOAD_1), new BranchInstruction(Instruction.OP_IFNE, +6), new ConstantInstruction(Instruction.OP_INVOKEVIRTUAL, 10), new SimpleInstruction(Instruction.OP_ARETURN), }; // return Class.forName(v0).getComponentType(). private final Instruction[] DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS2 = new Instruction[] { new VariableInstruction(Instruction.OP_ALOAD_0), new ConstantInstruction(Instruction.OP_INVOKESTATIC, 0), new ConstantInstruction(Instruction.OP_INVOKEVIRTUAL, 10), new SimpleInstruction(Instruction.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 ClassVisitor extraClassVisitor; private final InstructionSequenceMatcher constantClassForNameMatcher = new InstructionSequenceMatcher( CLASS_FOR_NAME_CONSTANTS, CONSTANT_CLASS_FOR_NAME_INSTRUCTIONS); private final InstructionSequenceMatcher constantClassForName3Matcher = new InstructionSequenceMatcher( CLASS_FOR_NAME_CONSTANTS, CONSTANT_CLASS_FOR_NAME_3_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); // Used to prefilter classes to avoid visiting all instructions in all classes. private final ClassConstantClassFilter classConstantClassFilter = new ClassConstantClassFilter( new FixedStringMatcher(NAME_JAVA_LANG_CLASS), new AllMethodVisitor( new AllAttributeVisitor( new AttributeNameFilter(Attribute.CODE, new AllInstructionVisitor(this))))); // 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, libraryClassPool, missingNotePrinter, dependencyWarningPrinter, notePrinter, noteExceptionMatcher, null); } /** * 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, ClassVisitor extraClassVisitor) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.missingNotePrinter = missingNotePrinter; this.dependencyWarningPrinter = dependencyWarningPrinter; this.notePrinter = notePrinter; this.noteExceptionMatcher = noteExceptionMatcher; this.extraClassVisitor = extraClassVisitor; } @Override public void visitAnyClass(Clazz clazz) {} @Override public void visitProgramClass(ProgramClass programClass) { programClass.accept(classConstantClassFilter); } // Implementations for InstructionVisitor. @Override 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); if (classForNameCastMatcher.isMatching()) { // Match found. Print out a note about the construct. clazz.constantPoolEntryAccept(classForNameCastMatcher.matchedConstantIndex(X), this); } // Try to match the Class.forName("SomeClass") or the Class.forName("SomeClass", true/false, // classLoader) construct. // Assumes the ClassLoader is obtained by loading the class and subsequently calling // "getClassLoader" on it. instruction.accept( clazz, method, codeAttribute, offset, new MultiInstructionVisitor(constantClassForNameMatcher, constantClassForName3Matcher)); if (constantClassForNameMatcher.isMatching() || constantClassForName3Matcher.isMatching()) { // Match found. Initialize the matched string constant. InstructionSequenceMatcher matchedMatcher = constantClassForNameMatcher.isMatching() ? constantClassForNameMatcher : constantClassForName3Matcher; clazz.constantPoolEntryAccept(matchedMatcher.matchedConstantIndex(X), this); // Don't look for the dynamic construct. classForNameCastMatcher.reset(); // There can be no other reflection patters at this offset. return; } // Try to match the javac .class construct. instruction.accept(clazz, method, codeAttribute, offset, dotClassJavacMatcher); if (dotClassJavacMatcher.isMatching() && isDotClassMethodref(clazz, dotClassJavacMatcher.matchedConstantIndex(0))) { // Match found. Initialize the matched string constant. clazz.constantPoolEntryAccept(dotClassJavacMatcher.matchedConstantIndex(X), this); // There can be no other reflection patters at this offset. return; } // Try to match the jikes .class construct. instruction.accept(clazz, method, codeAttribute, offset, dotClassJikesMatcher); if (dotClassJikesMatcher.isMatching() && isDotClassMethodref(clazz, dotClassJikesMatcher.matchedConstantIndex(0))) { // Match found. Initialize 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); if (stringConstant.referencedClass != null && extraClassVisitor != null) { stringConstant.referencedClass.accept(extraClassVisitor); } } /** 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 (notePrinter != null && (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 null 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy