proguard.classfile.util.DynamicMemberReferenceInitializer 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 static proguard.classfile.ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_INTEGER_FIELD_UPDATER;
import static proguard.classfile.ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_LONG_FIELD_UPDATER;
import static proguard.classfile.ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_REFERENCE_FIELD_UPDATER;
import proguard.classfile.ClassConstants;
import proguard.classfile.ClassPool;
import proguard.classfile.Clazz;
import proguard.classfile.JavaTypeConstants;
import proguard.classfile.LibraryClass;
import proguard.classfile.LibraryField;
import proguard.classfile.LibraryMethod;
import proguard.classfile.Member;
import proguard.classfile.Method;
import proguard.classfile.ProgramClass;
import proguard.classfile.ProgramField;
import proguard.classfile.ProgramMethod;
import proguard.classfile.TypeConstants;
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.FieldrefConstant;
import proguard.classfile.constant.MethodrefConstant;
import proguard.classfile.constant.StringConstant;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.editor.CodeAttributeEditor;
import proguard.classfile.editor.ConstantPoolEditor;
import proguard.classfile.editor.InstructionSequenceBuilder;
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.InstructionVisitor;
import proguard.classfile.visitor.AllFieldVisitor;
import proguard.classfile.visitor.AllMethodVisitor;
import proguard.classfile.visitor.ClassConstantClassFilter;
import proguard.classfile.visitor.ClassVisitor;
import proguard.classfile.visitor.MemberDescriptorFilter;
import proguard.classfile.visitor.MemberNameFilter;
import proguard.classfile.visitor.MemberVisitor;
import proguard.util.CollectionMatcher;
import proguard.util.StringMatcher;
/**
* This {@link AttributeVisitor} initializes any constant class member references of all code that
* it visits. It currently handles invocations of Class#get[Declared]{Field,Constructor,Method} and
* Atomic{Integer,Long,Reference}FieldUpdater.newUpdater with constant string arguments. It lets the
* corresponding string constants refer to their class members in the program class pool or in the
* library class pool. It may create new string constants and update the code, in order to avoid
* clashes between identically named class members.
*
* The class hierarchy and references must be initialized before using this visitor.
*
*
It's more efficient to use as a {@link ClassVisitor} than an {@link InstructionVisitor}.
*
* @see ClassSuperHierarchyInitializer
* @see ClassReferenceInitializer
* @author Eric Lafortune
*/
public class DynamicMemberReferenceInitializer
implements ClassVisitor, AttributeVisitor, InstructionVisitor, ConstantVisitor, MemberVisitor {
// *
private static final boolean DEBUG = false;
/*/
private static boolean DEBUG = System.getProperty("dmri") != null;
//*/
private static final int CLASS_INDEX = InstructionSequenceMatcher.A;
private static final int MEMBER_NAME_INDEX = InstructionSequenceMatcher.B;
private static final int MEMBER_TYPE_INDEX = InstructionSequenceMatcher.C;
private final ClassPool programClassPool;
private final ClassPool libraryClassPool;
private final WarningPrinter notePrinter;
private final StringMatcher noteFieldExceptionMatcher;
private final StringMatcher noteMethodExceptionMatcher;
// Retrieving fields or methods from known, constant classes.
private final InstructionSequenceMatcher knownIntegerUpdaterMatcher;
private final InstructionSequenceMatcher knownLongUpdaterMatcher;
private final InstructionSequenceMatcher knownReferenceUpdaterMatcher;
// Retrieving fields or methods from unknown classes.
private final InstructionSequenceMatcher unknownIntegerUpdaterMatcher;
private final InstructionSequenceMatcher unknownLongUpdaterMatcher;
private final InstructionSequenceMatcher unknownReferenceUpdaterMatcher;
private final MyDynamicMemberFinder dynamicMemberFinder = new MyDynamicMemberFinder();
private final MemberFinder memberFinder = new MemberFinder(true);
private final MemberFinder declaredMemberFinder = new MemberFinder(false);
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
private final MemberVisitor extraMemberVisitor;
// Used to prefilter classes to avoid visiting all instructions in all classes.
private final ClassConstantClassFilter classConstantClassFilter =
new ClassConstantClassFilter(
new CollectionMatcher(
NAME_JAVA_LANG_CLASS,
NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_INTEGER_FIELD_UPDATER,
NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_LONG_FIELD_UPDATER,
NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_REFERENCE_FIELD_UPDATER),
new AllMethodVisitor(
new AllAttributeVisitor(new AttributeNameFilter(Attribute.CODE, this))));
// Fields acting as parameters for the visitors.
private Clazz referencedClass;
/** Creates a new DynamicMemberReferenceInitializer. */
public DynamicMemberReferenceInitializer(
ClassPool programClassPool,
ClassPool libraryClassPool,
WarningPrinter notePrinter,
StringMatcher noteFieldExceptionMatcher,
StringMatcher noteMethodExceptionMatcher) {
this(
programClassPool,
libraryClassPool,
notePrinter,
noteFieldExceptionMatcher,
noteMethodExceptionMatcher,
null);
}
/** Creates a new DynamicMemberReferenceInitializer. */
public DynamicMemberReferenceInitializer(
ClassPool programClassPool,
ClassPool libraryClassPool,
WarningPrinter notePrinter,
StringMatcher noteFieldExceptionMatcher,
StringMatcher noteMethodExceptionMatcher,
MemberVisitor extraMemberVisitor) {
this.programClassPool = programClassPool;
this.libraryClassPool = libraryClassPool;
this.notePrinter = notePrinter;
this.noteFieldExceptionMatcher = noteFieldExceptionMatcher;
this.noteMethodExceptionMatcher = noteMethodExceptionMatcher;
this.extraMemberVisitor = extraMemberVisitor;
// Create the instruction sequences and matchers.
InstructionSequenceBuilder builder =
new InstructionSequenceBuilder(programClassPool, libraryClassPool);
// AtomicIntegerFieldUpdater.newUpdater(A.class, "someField").
Instruction[] knownIntegerUpdaterInstructions =
builder
.ldc_(CLASS_INDEX)
.ldc_(MEMBER_NAME_INDEX)
.invokestatic(
ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_INTEGER_FIELD_UPDATER,
ClassConstants.METHOD_NAME_NEW_UPDATER,
ClassConstants.METHOD_TYPE_NEW_INTEGER_UPDATER)
.instructions();
// AtomicLongFieldUpdater.newUpdater(A.class, "someField").
Instruction[] knownLongUpdaterInstructions =
builder
.ldc_(CLASS_INDEX)
.ldc_(MEMBER_NAME_INDEX)
.invokestatic(
ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_LONG_FIELD_UPDATER,
ClassConstants.METHOD_NAME_NEW_UPDATER,
ClassConstants.METHOD_TYPE_NEW_LONG_UPDATER)
.instructions();
// AtomicReferenceFieldUpdater.newUpdater(A.class, B.class, "someField").
Instruction[] knownReferenceUpdaterInstructions =
builder
.ldc_(CLASS_INDEX)
.ldc_(MEMBER_TYPE_INDEX)
.ldc_(MEMBER_NAME_INDEX)
.invokestatic(
ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_REFERENCE_FIELD_UPDATER,
ClassConstants.METHOD_NAME_NEW_UPDATER,
ClassConstants.METHOD_TYPE_NEW_REFERENCE_UPDATER)
.instructions();
// AtomicIntegerFieldUpdater.newUpdater(..., "someField").
Instruction[] unknownIntegerUpdaterInstructions =
builder
.ldc_(MEMBER_NAME_INDEX)
.invokestatic(
ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_INTEGER_FIELD_UPDATER,
ClassConstants.METHOD_NAME_NEW_UPDATER,
ClassConstants.METHOD_TYPE_NEW_INTEGER_UPDATER)
.instructions();
// AtomicLongFieldUpdater.newUpdater(..., "someField").
Instruction[] unknownLongUpdaterInstructions =
builder
.ldc_(MEMBER_NAME_INDEX)
.invokestatic(
ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_LONG_FIELD_UPDATER,
ClassConstants.METHOD_NAME_NEW_UPDATER,
ClassConstants.METHOD_TYPE_NEW_LONG_UPDATER)
.instructions();
// AtomicReferenceFieldUpdater.newUpdater(..., "someField").
final Instruction[] unknownReferenceUpdaterInstructions =
builder
.ldc_(MEMBER_NAME_INDEX)
.invokestatic(
ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_REFERENCE_FIELD_UPDATER,
ClassConstants.METHOD_NAME_NEW_UPDATER,
ClassConstants.METHOD_TYPE_NEW_REFERENCE_UPDATER)
.instructions();
Constant[] constants = builder.constants();
knownIntegerUpdaterMatcher =
new InstructionSequenceMatcher(constants, knownIntegerUpdaterInstructions);
knownLongUpdaterMatcher =
new InstructionSequenceMatcher(constants, knownLongUpdaterInstructions);
knownReferenceUpdaterMatcher =
new InstructionSequenceMatcher(constants, knownReferenceUpdaterInstructions);
unknownIntegerUpdaterMatcher =
new InstructionSequenceMatcher(constants, unknownIntegerUpdaterInstructions);
unknownLongUpdaterMatcher =
new InstructionSequenceMatcher(constants, unknownLongUpdaterInstructions);
unknownReferenceUpdaterMatcher =
new InstructionSequenceMatcher(constants, unknownReferenceUpdaterInstructions);
}
@Override
public void visitAnyClass(Clazz clazz) {
// Only ProgramClasses contain code.
}
@Override
public void visitProgramClass(ProgramClass programClass) {
programClass.accept(classConstantClassFilter);
}
// Implementations for AttributeVisitor.
@Override
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
@Override
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) {
if (DEBUG) {
System.out.println(
"DynamicMemberReferenceInitializer: "
+ clazz.getName()
+ "."
+ method.getName(clazz)
+ method.getDescriptor(clazz));
}
// Set up the code attribute editor.
codeAttributeEditor.reset(codeAttribute.u4codeLength);
// Find the dynamic class member references.
codeAttribute.instructionsAccept(clazz, method, this);
// Apply any changes to the code.
codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
}
// Implementations for InstructionVisitor.
@Override
public void visitAnyInstruction(
Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int offset,
Instruction instruction) {
// Try to match get[Declared]{Field,Constructor,Method} constructs.
instruction.accept(clazz, method, codeAttribute, offset, dynamicMemberFinder);
// Try to match the AtomicIntegerFieldUpdater.newUpdater(
// SomeClass.class, "someField") construct.
matchGetMember(
clazz,
method,
codeAttribute,
offset,
instruction,
knownIntegerUpdaterMatcher,
unknownIntegerUpdaterMatcher,
true,
false,
false,
"" + TypeConstants.INT);
// Try to match the AtomicLongFieldUpdater.newUpdater(
// SomeClass.class, "someField") construct.
matchGetMember(
clazz,
method,
codeAttribute,
offset,
instruction,
knownLongUpdaterMatcher,
unknownLongUpdaterMatcher,
true,
false,
false,
"" + TypeConstants.LONG);
// Try to match the AtomicReferenceFieldUpdater.newUpdater(
// SomeClass.class, SomeClass.class, "someField") construct.
matchGetMember(
clazz,
method,
codeAttribute,
offset,
instruction,
knownReferenceUpdaterMatcher,
unknownReferenceUpdaterMatcher,
true,
false,
false,
null);
}
/**
* Tries to match the next instruction and fills out the string constant or prints out a note
* accordingly.
*/
private void matchGetMember(
Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int offset,
Instruction instruction,
InstructionSequenceMatcher constantSequenceMatcher,
InstructionSequenceMatcher variableSequenceMatcher,
boolean isField,
boolean isConstructor,
boolean isDeclared,
String memberDescriptor) {
if (constantSequenceMatcher != null) {
// Try to match the next instruction in the constant sequence.
instruction.accept(clazz, method, codeAttribute, offset, constantSequenceMatcher);
// Did we find a match to fill out the string constant?
if (constantSequenceMatcher.isMatching()) {
// Retrieve the offset of the instruction that loads the member
// name. It's currently always the last but one instruction.
int memberNameInstructionOffset =
constantSequenceMatcher.matchedInstructionOffset(
constantSequenceMatcher.instructionCount() - 2);
// Get the member's class.
int classIndex = constantSequenceMatcher.matchedConstantIndex(CLASS_INDEX);
clazz.constantPoolEntryAccept(classIndex, this);
if (referencedClass != null) {
// Get the field's type, if applicable.
int typeClassIndex = constantSequenceMatcher.matchedConstantIndex(MEMBER_TYPE_INDEX);
if (typeClassIndex > 0) {
memberDescriptor =
ClassUtil.internalTypeFromClassName(clazz.getClassName(typeClassIndex));
}
// Get the member's name.
int memberNameIndex = constantSequenceMatcher.matchedConstantIndex(MEMBER_NAME_INDEX);
String memberName = clazz.getStringString(memberNameIndex);
// Create a new string constant and update the instruction.
initializeDynamicMemberReference(
clazz,
memberNameInstructionOffset,
referencedClass,
memberName,
memberDescriptor,
isField,
isConstructor,
isDeclared);
}
// Don't look for the dynamic construct.
variableSequenceMatcher.reset();
}
}
// Try to match the next instruction in the variable sequence.
instruction.accept(clazz, method, codeAttribute, offset, variableSequenceMatcher);
// Did we find a match to print out a note?
if (variableSequenceMatcher.isMatching()) {
int memberNameIndex = variableSequenceMatcher.matchedConstantIndex(MEMBER_NAME_INDEX);
String memberName = clazz.getStringString(memberNameIndex);
// Print out a note about the dynamic invocation.
printDynamicMemberAccessNote(
clazz, memberName, memberDescriptor, isField, isConstructor, isDeclared);
}
}
// Implementations for ConstantVisitor.
public void visitClassConstant(Clazz clazz, ClassConstant classConstant) {
// Remember the referenced class.
referencedClass =
ClassUtil.isInternalArrayType(classConstant.getName(clazz))
? null
: classConstant.referencedClass;
}
// Small utility methods.
/**
* Creates a new string constant for the specified referenced class member, and updates the
* instruction that loads it.
*/
private void initializeDynamicMemberReference(
Clazz clazz,
int memberNameInstructionOffset,
Clazz referencedClass,
String memberName,
String memberDescriptor,
boolean isField,
boolean isConstructor,
boolean isDeclared) {
// See if we can find the referenced class member locally, or
// somewhere in the hierarchy.
MemberFinder referencedMemberFinder = isDeclared ? declaredMemberFinder : memberFinder;
Member referencedMember =
referencedMemberFinder.findMember(referencedClass, memberName, memberDescriptor, isField);
if (DEBUG) {
System.out.println(
"DynamicMemberReferenceInitializer: ["
+ clazz.getName()
+ "] matched string ["
+ memberName
+ "]: in ["
+ referencedClass
+ "] -> ["
+ referencedMember
+ "]");
}
if (referencedMember != null) {
if (!isDeclared) {
referencedClass = referencedMemberFinder.correspondingClass();
}
// Update the string constant.
// stringConstant.referencedMember = referencedMember;
// stringConstant.referencedClass = referencedClass;
// Create a new string constant with the found references.
int stringConstantIndex =
new ConstantPoolEditor((ProgramClass) clazz)
.addStringConstant(memberName, referencedClass, referencedMember);
// Update the instruction.
codeAttributeEditor.replaceInstruction(
memberNameInstructionOffset,
new ConstantInstruction(Instruction.OP_LDC, stringConstantIndex));
if (extraMemberVisitor != null) {
referencedMember.accept(referencedClass, extraMemberVisitor);
}
}
}
/** Prints out a note on the matched dynamic invocation of a constructor. */
private void printDynamicConstructorAccessNote(
Clazz clazz, Clazz referencedClass, String memberDescriptor, boolean isDeclared) {
// Print out a note about the dynamic invocation.
if (notePrinter != null && notePrinter.accepts(clazz.getName())) {
// Is the class member name in the list of exceptions?
if (noteMethodExceptionMatcher == null
|| !noteMethodExceptionMatcher.matches(ClassConstants.METHOD_NAME_INIT)) {
// Print out the actual note.
notePrinter.print(
clazz.getName(),
"Note: "
+ ClassUtil.externalClassName(clazz.getName())
+ " retrieves a "
+ (isDeclared ? "declared " : "")
+ "constructor '"
+ ClassConstants.METHOD_NAME_INIT
+ JavaTypeConstants.METHOD_ARGUMENTS_OPEN
+ ClassUtil.externalMethodArguments(memberDescriptor)
+ JavaTypeConstants.METHOD_ARGUMENTS_CLOSE
+ "' dynamically");
// Print out notes about potential candidates.
ClassVisitor classVisitor =
new AllMethodVisitor(
new MemberNameFilter(
ClassConstants.METHOD_NAME_INIT,
new MemberDescriptorFilter(memberDescriptor, this)));
if (referencedClass != null) {
referencedClass.hierarchyAccept(true, !isDeclared, false, false, classVisitor);
} else {
programClassPool.classesAcceptAlphabetically(classVisitor);
libraryClassPool.classesAcceptAlphabetically(classVisitor);
}
}
}
}
/** Prints out a note on the matched dynamic access to a class member. */
private void printDynamicMemberAccessNote(
Clazz clazz,
String memberName,
String memberDescriptor,
boolean isField,
boolean isConstructor,
boolean isDeclared) {
// Print out a note about the dynamic invocation.
if (notePrinter != null && notePrinter.accepts(clazz.getName())) {
// Is the class member name in the list of exceptions?
StringMatcher noteExceptionMatcher =
isField ? noteFieldExceptionMatcher : noteMethodExceptionMatcher;
if (noteExceptionMatcher == null || !noteExceptionMatcher.matches(memberName)) {
// Compose the external member name and partial descriptor.
String externalMemberDescription = memberName;
if (!isField) {
externalMemberDescription +=
JavaTypeConstants.METHOD_ARGUMENTS_OPEN
+ ClassUtil.externalMethodArguments(memberDescriptor)
+ JavaTypeConstants.METHOD_ARGUMENTS_CLOSE;
}
// Print out the actual note.
notePrinter.print(
clazz.getName(),
"Note: "
+ ClassUtil.externalClassName(clazz.getName())
+ " accesses a "
+ (isDeclared ? "declared " : "")
+ (isField ? "field" : isConstructor ? "constructor" : "method")
+ " '"
+ externalMemberDescription
+ "' dynamically");
// Print out notes about potential candidates.
ClassVisitor classVisitor;
if (isField) {
classVisitor =
memberDescriptor == null
? new AllFieldVisitor(new MemberNameFilter(memberName, this))
: new AllFieldVisitor(
new MemberNameFilter(
memberName, new MemberDescriptorFilter(memberDescriptor, this)));
} else {
classVisitor =
new AllMethodVisitor(
new MemberNameFilter(
memberName, new MemberDescriptorFilter(memberDescriptor, this)));
}
programClassPool.classesAcceptAlphabetically(classVisitor);
libraryClassPool.classesAcceptAlphabetically(classVisitor);
}
}
}
// Implementations for MemberVisitor.
public void visitProgramField(ProgramClass programClass, ProgramField programField) {
notePrinter.note(
programClass.getName(),
" Maybe this is program field '"
+ ClassUtil.externalFullClassDescription(0, programClass.getName())
+ " { "
+ ClassUtil.externalFullFieldDescription(
0, programField.getName(programClass), programField.getDescriptor(programClass))
+ "; }'");
}
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) {
notePrinter.note(
programClass.getName(),
" Maybe this is program method '"
+ ClassUtil.externalFullClassDescription(0, programClass.getName())
+ " { "
+ ClassUtil.externalFullMethodDescription(
programClass.getName(),
0,
programMethod.getName(programClass),
programMethod.getDescriptor(programClass))
+ "; }'");
}
public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {
notePrinter.note(
libraryClass.getName(),
" Maybe this is library field '"
+ ClassUtil.externalFullClassDescription(0, libraryClass.getName())
+ " { "
+ ClassUtil.externalFullFieldDescription(
0, libraryField.getName(libraryClass), libraryField.getDescriptor(libraryClass))
+ "; }'");
}
public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {
notePrinter.note(
libraryClass.getName(),
" Maybe this is library method '"
+ ClassUtil.externalFullClassDescription(0, libraryClass.getName())
+ " { "
+ ClassUtil.externalFullMethodDescription(
libraryClass.getName(),
0,
libraryMethod.getName(libraryClass),
libraryMethod.getDescriptor(libraryClass))
+ "; }'");
}
/**
* This InstructionVisitor finds get[Declared]{Field,Constructor,Method} constructs with constant
* arguments. It then makes sure the class member name strings point to the class members, or it
* prints out notes about the possible alternatives.
*/
private class MyDynamicMemberFinder implements InstructionVisitor, ConstantVisitor {
private static final int LABEL_START = 0; // ldc SomeClass.class
private static final int LABEL_LOAD_MEMBER_NAME = 1; // [ ldc "someMethod"/"someField" ]
private static final int LABEL_LOAD_CLASS_ARRAY_SIZE = 2; // [ sipush #someParameterCount
private static final int LABEL_CREATE_CLASS_ARRAY = 3; // anewarray java/lang/Class
private static final int LABEL_DUP_CLASS_ARRAY = 4; // [ dup
private static final int LABEL_LOAD_PARAMETER_INDEX = 5; // sipush #someParameterIndex
private static final int LABEL_LOAD_PARAMETER_TYPE =
6; // ldc SomeParameterClass.class / getstatic java/lang/SomePrimitive.TYPE
private static final int LABEL_STORE_PARAMETER = 7; // aastore ]* ] / aconst_null
private static final int LABEL_GET_MEMBER =
8; // invokevirtual java/lang/Class.getField/getConstructor/getMethod
private int label;
private int instructionOffset;
private int memberNameInstructionOffset;
private Clazz referencedClass;
private String memberName;
private int parameterCount;
private int parameterIndex;
private final StringBuffer parameterTypes = new StringBuffer();
public void reset() {
label = LABEL_START;
referencedClass = null;
memberName = null;
parameterTypes.setLength(0);
}
// Implementations for InstructionVisitor.
public void visitAnyInstruction(
Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int offset,
Instruction instruction) {
if (DEBUG) {
System.out.println("Label [" + label + "] A " + instruction.toString(clazz, offset));
}
reset();
}
@Override
public void visitVariableInstruction(
Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int offset,
VariableInstruction variableInstruction) {
if (DEBUG) {
System.out.println(
"Label [" + label + "] V " + variableInstruction.toString(clazz, offset));
}
switch (variableInstruction.canonicalOpcode()) {
case Instruction.OP_ASTORE:
case Instruction.OP_ALOAD:
// Ignore astore/aload instructions.
break;
default:
reset();
break;
}
}
public void visitSimpleInstruction(
Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int offset,
SimpleInstruction simpleInstruction) {
if (DEBUG) {
System.out.println("Label [" + label + "] S " + simpleInstruction.toString(clazz, offset));
}
int transition = label | simpleInstruction.canonicalOpcode() << 8;
switch (transition) {
case LABEL_START | Instruction.OP_ICONST_0 << 8:
case LABEL_LOAD_MEMBER_NAME | Instruction.OP_ICONST_0 << 8:
case LABEL_CREATE_CLASS_ARRAY | Instruction.OP_ICONST_0 << 8:
case LABEL_DUP_CLASS_ARRAY | Instruction.OP_ICONST_0 << 8:
case LABEL_LOAD_PARAMETER_TYPE | Instruction.OP_ICONST_0 << 8:
case LABEL_STORE_PARAMETER | Instruction.OP_ICONST_0 << 8:
case LABEL_GET_MEMBER | Instruction.OP_ICONST_0 << 8:
// This could be the start of creating a class array.
reset();
parameterCount = simpleInstruction.constant;
label = LABEL_CREATE_CLASS_ARRAY;
break;
case LABEL_LOAD_CLASS_ARRAY_SIZE | Instruction.OP_ICONST_0 << 8:
parameterCount = simpleInstruction.constant;
label = LABEL_CREATE_CLASS_ARRAY;
break;
case LABEL_LOAD_CLASS_ARRAY_SIZE | Instruction.OP_ACONST_NULL << 8:
parameterCount = 0;
label = LABEL_GET_MEMBER;
break;
case LABEL_DUP_CLASS_ARRAY | Instruction.OP_DUP << 8:
label = LABEL_LOAD_PARAMETER_INDEX;
break;
case LABEL_LOAD_PARAMETER_INDEX | Instruction.OP_ICONST_0 << 8:
// Is it pushing the expected parameter index?
if (parameterIndex == simpleInstruction.constant) {
label = LABEL_LOAD_PARAMETER_TYPE;
} else {
// This could be the start of creating a class array.
reset();
parameterCount = simpleInstruction.constant;
label = LABEL_CREATE_CLASS_ARRAY;
}
break;
case LABEL_STORE_PARAMETER | Instruction.OP_AASTORE << 8:
// Are we still expecting more parameters?
label = ++parameterIndex < parameterCount ? LABEL_DUP_CLASS_ARRAY : LABEL_GET_MEMBER;
break;
default:
reset();
break;
}
}
public void visitConstantInstruction(
Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int offset,
ConstantInstruction constantInstruction) {
if (DEBUG) {
System.out.println(
"Label [" + label + "] C " + constantInstruction.toString(clazz, offset));
}
// Let the constant figure out the transition.
switch (constantInstruction.canonicalOpcode()) {
case Instruction.OP_LDC:
instructionOffset = offset;
clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
break;
case Instruction.OP_CHECKCAST:
// We simply ignore any casts, typically to Class[],
// but maybe even to Class or String.
break;
case Instruction.OP_GETSTATIC:
case Instruction.OP_INVOKEVIRTUAL:
case Instruction.OP_INVOKESTATIC:
clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
break;
case Instruction.OP_ANEWARRAY:
if (label == LABEL_CREATE_CLASS_ARRAY) {
clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
} else {
reset();
}
break;
default:
reset();
break;
}
}
// Implementations for ConstantVisitor.
public void visitAnyConstant(Clazz clazz, Constant constant) {
reset();
}
public void visitClassConstant(Clazz clazz, ClassConstant classConstant) {
// Argument of ldc or anewarray instruction.
switch (label) {
case LABEL_START:
referencedClass = classConstant.referencedClass;
label = LABEL_LOAD_MEMBER_NAME;
break;
case LABEL_CREATE_CLASS_ARRAY:
if (classConstant.getName(clazz).equals(ClassConstants.NAME_JAVA_LANG_CLASS)) {
parameterIndex = 0;
label = parameterCount > 0 ? LABEL_DUP_CLASS_ARRAY : LABEL_GET_MEMBER;
} else {
referencedClass = classConstant.referencedClass;
label = LABEL_LOAD_MEMBER_NAME;
}
break;
case LABEL_LOAD_PARAMETER_TYPE:
String parameterType = ClassUtil.internalTypeFromClassType(classConstant.getName(clazz));
parameterTypes.append(parameterType);
label = LABEL_STORE_PARAMETER;
break;
default:
// For other states, we'll treat this as a potential
// initial class name.
referencedClass = classConstant.referencedClass;
label = LABEL_LOAD_MEMBER_NAME;
break;
}
}
public void visitStringConstant(Clazz clazz, StringConstant stringConstant) {
// Argument of ldc instruction.
switch (label) {
case LABEL_START:
// Also handle calls to Class.forName("className") that have
// already been processed by the DynamicClassReferenceInitializer.
referencedClass = stringConstant.referencedClass;
break;
case LABEL_LOAD_MEMBER_NAME:
break;
default:
// For other states, we'll treat this as a potential
// initial method name, without a known class.
referencedClass = null;
break;
}
// Whatever state, we'll treat this as a potential method name.
memberNameInstructionOffset = instructionOffset;
memberName = stringConstant.getString(clazz);
label = LABEL_LOAD_CLASS_ARRAY_SIZE;
}
public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) {
// Argument of getstatic instruction.
switch (label) {
case LABEL_LOAD_PARAMETER_TYPE:
String className = fieldrefConstant.getClassName(clazz);
String fieldName = fieldrefConstant.getName(clazz);
String fieldType = fieldrefConstant.getType(clazz);
if (className.startsWith(ClassConstants.PACKAGE_JAVA_LANG)
&& fieldName.equals(ClassConstants.FIELD_NAME_TYPE)
&& fieldType.equals(ClassConstants.FIELD_TYPE_TYPE)) {
char parameterType = ClassUtil.internalPrimitiveTypeFromNumericClassName(className);
parameterTypes.append(parameterType);
label = LABEL_STORE_PARAMETER;
} else {
reset();
}
break;
default:
reset();
break;
}
}
public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) {
// Argument of invokevirtual/invokestatic instruction.
String className = methodrefConstant.getClassName(clazz);
if (className.equals(ClassConstants.NAME_JAVA_LANG_CLASS)) {
String methodName = methodrefConstant.getName(clazz);
String methodType = methodrefConstant.getType(clazz);
if (label == LABEL_LOAD_CLASS_ARRAY_SIZE
&& methodType.equals(ClassConstants.METHOD_TYPE_CLASS_GET_FIELD)
&& memberName != null) {
if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_FIELD)) {
resolveMemberString(clazz, true, false, false);
} else if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_DECLARED_FIELD)) {
resolveMemberString(clazz, true, false, true);
} else {
reset();
}
} else if (label == LABEL_GET_MEMBER
&& methodType.equals(ClassConstants.METHOD_TYPE_CLASS_GET_CONSTRUCTOR)) {
if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_CONSTRUCTOR)) {
resolveMemberString(clazz, false, true, false);
} else if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_DECLARED_CONSTRUCTOR)) {
resolveMemberString(clazz, false, true, true);
} else {
reset();
}
} else if (label == LABEL_GET_MEMBER
&& methodType.equals(ClassConstants.METHOD_TYPE_CLASS_GET_METHOD)
&& memberName != null) {
if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_METHOD)) {
resolveMemberString(clazz, false, false, false);
} else if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_DECLARED_METHOD)) {
resolveMemberString(clazz, false, false, true);
} else {
reset();
}
} else if (label == LABEL_LOAD_CLASS_ARRAY_SIZE
&& methodName.equals(ClassConstants.METHOD_NAME_CLASS_FOR_NAME)
&& methodType.equals(ClassConstants.METHOD_TYPE_CLASS_FOR_NAME)) {
label = LABEL_LOAD_MEMBER_NAME;
} else {
reset();
}
} else {
reset();
}
}
/**
* Links the referenced class member in the string, or prints out notes about the possible
* alternatives.
*/
private void resolveMemberString(
Clazz clazz, boolean isField, boolean isConstructor, boolean isDeclared) {
String memberDescriptor =
isField
? null
: TypeConstants.METHOD_ARGUMENTS_OPEN
+ parameterTypes.toString()
+ TypeConstants.METHOD_ARGUMENTS_CLOSE
+ "L***;";
if (DEBUG) {
System.out.println("DynamicMemberReferenceInitializer: found member access");
System.out.println(" isField = " + isField);
System.out.println(" isConstructor = " + isConstructor);
System.out.println(" isDeclared = " + isDeclared);
System.out.println(
" referenced class = "
+ (referencedClass == null ? "(none)" : "[" + referencedClass.getName() + "]"));
System.out.println(
" member name = " + (memberName == null ? "(none)" : "[" + memberName + "]"));
System.out.println(
" member descriptor = "
+ (memberDescriptor == null ? "(none)" : "[" + memberDescriptor + "]"));
}
if (referencedClass != null) {
if (isConstructor) {
// We currently can't fill out some reference to a
// constructor. Just print out notes instead.
printDynamicConstructorAccessNote(clazz, referencedClass, memberDescriptor, isDeclared);
} else {
// Create a new string constant and update the instruction.
initializeDynamicMemberReference(
clazz,
memberNameInstructionOffset,
referencedClass,
memberName,
memberDescriptor,
isField,
isConstructor,
isDeclared);
}
} else {
// Print out notes about the method in some unknown class.
printDynamicMemberAccessNote(
clazz,
isConstructor ? ClassConstants.METHOD_NAME_INIT : memberName,
memberDescriptor,
isField,
isConstructor,
isDeclared);
}
reset();
}
}
}