blob: 413d885559f1ab21a5d235b649a68445d165cebd [file] [log] [blame]
/*
* 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;
}
}