proguard.classfile.util.DynamicClassReferenceInitializer Maven / Gradle / Ivy
Show all versions of proguard-core Show documentation
/*
* 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;
}
}