blob: b748c68e088dddde55b35d9ab628f7e79c02cf2f [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.optimize.evaluation;
import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.visitor.*;
import proguard.classfile.constant.ClassConstant;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.instruction.*;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import proguard.evaluation.*;
import proguard.evaluation.value.*;
import proguard.optimize.info.SimpleEnumMarker;
/**
* This ClassVisitor marks enums that can't be simplified due to the way they
* are used in the classes that it visits.
*
* @see SimpleEnumMarker
* @author Eric Lafortune
*/
public class SimpleEnumUseChecker
extends SimplifiedVisitor
implements ClassVisitor,
MemberVisitor,
AttributeVisitor,
InstructionVisitor,
ConstantVisitor,
ParameterVisitor
{
//*
private static final boolean DEBUG = false;
/*/
private static boolean DEBUG = System.getProperty("enum") != null;
//*/
private final PartialEvaluator partialEvaluator;
private final MemberVisitor methodCodeChecker = new AllAttributeVisitor(this);
private final ConstantVisitor invokedMethodChecker = new ReferencedMemberVisitor(this);
private final ConstantVisitor parameterChecker = new ReferencedMemberVisitor(new AllParameterVisitor(this));
private final ClassVisitor complexEnumMarker = new SimpleEnumMarker(false);
private final ReferencedClassVisitor referencedComplexEnumMarker = new ReferencedClassVisitor(complexEnumMarker);
// Fields acting as parameters and return values for the visitor methods.
private int invocationOffset;
/**
* Creates a new SimpleEnumUseSimplifier.
*/
public SimpleEnumUseChecker()
{
this(new PartialEvaluator());
}
/**
* Creates a new SimpleEnumUseChecker.
* @param partialEvaluator the partial evaluator that will execute the code
* and provide information about the results.
*/
public SimpleEnumUseChecker(PartialEvaluator partialEvaluator)
{
this.partialEvaluator = partialEvaluator;
}
// Implementations for ClassVisitor.
public void visitProgramClass(ProgramClass programClass)
{
if ((programClass.getAccessFlags() & ClassConstants.ACC_ANNOTATTION) != 0)
{
// Unmark the simple enum classes in annotations.
programClass.methodsAccept(referencedComplexEnumMarker);
}
else
{
// Unmark the simple enum classes that are used in a complex way.
programClass.methodsAccept(methodCodeChecker);
}
}
// Implementations for AttributeVisitor.
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
// Evaluate the method.
partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
int codeLength = codeAttribute.u4codeLength;
// Check all traced instructions.
for (int offset = 0; offset < codeLength; offset++)
{
if (partialEvaluator.isTraced(offset))
{
Instruction instruction = InstructionFactory.create(codeAttribute.code,
offset);
instruction.accept(clazz, method, codeAttribute, offset, this);
// Check generalized stacks and variables at branch targets.
if (partialEvaluator.isBranchOrExceptionTarget(offset))
{
checkMixedStackEntriesBefore(offset);
checkMixedVariablesBefore(offset);
}
}
}
}
// Implementations for InstructionVisitor.
public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
{
switch (simpleInstruction.opcode)
{
case InstructionConstants.OP_AASTORE:
{
// Check if the instruction is storing a simple enum in a
// more general array.
if (!isPoppingSimpleEnumType(offset, 2))
{
if (DEBUG)
{
if (isPoppingSimpleEnumType(offset))
{
System.out.println("SimpleEnumUseChecker: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] stores enum ["+
partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType()+"] in more general array ["+
partialEvaluator.getStackBefore(offset).getTop(2).referenceValue().getType()+"]");
}
}
markPoppedComplexEnumType(offset);
}
break;
}
case InstructionConstants.OP_ARETURN:
{
// Check if the instruction is returning a simple enum as a
// more general type.
if (!isReturningSimpleEnumType(clazz, method))
{
if (DEBUG)
{
if (isPoppingSimpleEnumType(offset))
{
System.out.println("SimpleEnumUseChecker: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] returns enum [" +
partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType()+"] as more general type");
}
}
markPoppedComplexEnumType(offset);
}
break;
}
case InstructionConstants.OP_MONITORENTER:
case InstructionConstants.OP_MONITOREXIT:
{
// Make sure the popped type is not a simple enum type.
if (DEBUG)
{
if (isPoppingSimpleEnumType(offset))
{
System.out.println("SimpleEnumUseChecker: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] uses enum ["+
partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType()+"] as monitor");
}
}
markPoppedComplexEnumType(offset);
break;
}
}
}
public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
{
}
public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
{
switch (constantInstruction.opcode)
{
case InstructionConstants.OP_PUTSTATIC:
case InstructionConstants.OP_PUTFIELD:
{
// Check if the instruction is generalizing a simple enum to a
// different type.
invocationOffset = offset;
clazz.constantPoolEntryAccept(constantInstruction.constantIndex,
parameterChecker);
break;
}
case InstructionConstants.OP_INVOKEVIRTUAL:
{
// Check if the instruction is calling a simple enum.
String invokedMethodName =
clazz.getRefName(constantInstruction.constantIndex);
String invokedMethodType =
clazz.getRefType(constantInstruction.constantIndex);
int stackEntryIndex =
ClassUtil.internalMethodParameterSize(invokedMethodType);
if (isPoppingSimpleEnumType(offset, stackEntryIndex) &&
!isSupportedMethod(invokedMethodName,
invokedMethodType))
{
if (DEBUG)
{
System.out.println("SimpleEnumUseChecker: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] calls ["+partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue().getType()+"."+invokedMethodName+"]");
}
markPoppedComplexEnumType(offset, stackEntryIndex);
}
// Check if any of the parameters is generalizing a simple
// enum to a different type.
invocationOffset = offset;
clazz.constantPoolEntryAccept(constantInstruction.constantIndex,
parameterChecker);
break;
}
case InstructionConstants.OP_INVOKESPECIAL:
case InstructionConstants.OP_INVOKESTATIC:
case InstructionConstants.OP_INVOKEINTERFACE:
{
// Check if it is calling a method that we can't simplify.
clazz.constantPoolEntryAccept(constantInstruction.constantIndex,
invokedMethodChecker);
// Check if any of the parameters is generalizing a simple
// enum to a different type.
invocationOffset = offset;
clazz.constantPoolEntryAccept(constantInstruction.constantIndex,
parameterChecker);
break;
}
case InstructionConstants.OP_CHECKCAST:
case InstructionConstants.OP_INSTANCEOF:
{
// Check if the instruction is popping a different type.
if (!isPoppingExpectedType(offset,
clazz,
constantInstruction.constantIndex))
{
if (DEBUG)
{
if (isPoppingSimpleEnumType(offset))
{
System.out.println("SimpleEnumUseChecker: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] is casting or checking ["+
partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType()+"] as ["+
clazz.getClassName(constantInstruction.constantIndex)+"]");
}
}
// Make sure the popped type is not a simple enum type.
markPoppedComplexEnumType(offset);
// Make sure the checked type is not a simple enum type.
// We're somewhat arbitrarily skipping casts in static
// methods of simple enum classes, because they do occur
// in values() and valueOf(String), without obstructing
// simplification.
if (!isSimpleEnum(clazz) ||
(method.getAccessFlags() & ClassConstants.ACC_STATIC) == 0 ||
constantInstruction.opcode != InstructionConstants.OP_CHECKCAST)
{
if (DEBUG)
{
if (isSimpleEnum(((ClassConstant)((ProgramClass)clazz).getConstant(constantInstruction.constantIndex)).referencedClass))
{
System.out.println("SimpleEnumUseChecker: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] is casting or checking ["+
partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType()+"] as ["+
clazz.getClassName(constantInstruction.constantIndex)+"]");
}
}
markConstantComplexEnumType(clazz, constantInstruction.constantIndex);
}
}
break;
}
}
}
public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
{
switch (branchInstruction.opcode)
{
case InstructionConstants.OP_IFACMPEQ:
case InstructionConstants.OP_IFACMPNE:
{
// Check if the instruction is comparing different types.
if (!isPoppingIdenticalTypes(offset, 0, 1))
{
if (DEBUG)
{
if (isPoppingSimpleEnumType(offset, 0))
{
System.out.println("SimpleEnumUseChecker: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] compares ["+partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().getType()+"] to plain type");
}
if (isPoppingSimpleEnumType(offset, 1))
{
System.out.println("SimpleEnumUseChecker: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] compares ["+partialEvaluator.getStackBefore(offset).getTop(1).referenceValue().getType()+"] to plain type");
}
}
// Make sure the first popped type is not a simple enum type.
markPoppedComplexEnumType(offset, 0);
// Make sure the second popped type is not a simple enum type.
markPoppedComplexEnumType(offset, 1);
}
break;
}
}
}
public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
{
}
// Implementations for MemberVisitor.
public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {}
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
{
if (isSimpleEnum(programClass) &&
isUnsupportedMethod(programMethod.getName(programClass),
programMethod.getDescriptor(programClass)))
{
if (DEBUG)
{
System.out.println("SimpleEnumUseChecker: invocation of ["+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"]");
}
complexEnumMarker.visitProgramClass(programClass);
}
}
// Implementations for ParameterVisitor.
public void visitParameter(Clazz clazz, Member member, int parameterIndex, int parameterCount, int parameterOffset, int parameterSize, String parameterType, Clazz referencedClass)
{
// Check if the parameter is passing a simple enum as a more general
// type.
int stackEntryIndex = parameterSize - parameterOffset - 1;
if (ClassUtil.isInternalClassType(parameterType) &&
!isPoppingExpectedType(invocationOffset, stackEntryIndex,
ClassUtil.isInternalArrayType(parameterType) ?
parameterType :
ClassUtil.internalClassNameFromClassType(parameterType)))
{
if (DEBUG)
{
ReferenceValue poppedValue =
partialEvaluator.getStackBefore(invocationOffset).getTop(stackEntryIndex).referenceValue();
if (isSimpleEnumType(poppedValue))
{
System.out.println("SimpleEnumUseChecker: ["+poppedValue.getType()+"] "+
(member instanceof Field ?
("is stored as more general type ["+parameterType+"] in field ["+clazz.getName()+"."+member.getName(clazz)+"]") :
("is passed as more general argument #"+parameterIndex+" ["+parameterType+"] to ["+clazz.getName()+"."+member.getName(clazz)+"]")));
}
}
// Make sure the popped type is not a simple enum type.
markPoppedComplexEnumType(invocationOffset, stackEntryIndex);
}
}
// Small utility methods.
/**
* Returns whether the specified enum method is supported for simple enums.
*/
private boolean isSupportedMethod(String name, String type)
{
return
name.equals(ClassConstants.METHOD_NAME_ORDINAL) &&
type.equals(ClassConstants.METHOD_TYPE_ORDINAL) ||
name.equals(ClassConstants.METHOD_NAME_CLONE) &&
type.equals(ClassConstants.METHOD_TYPE_CLONE);
}
/**
* Returns whether the specified enum method is unsupported for simple enums.
*/
private boolean isUnsupportedMethod(String name, String type)
{
return
name.equals(ClassConstants.METHOD_NAME_VALUEOF);
}
/**
* Unmarks simple enum classes that are mixed with incompatible reference
* types in the stack before the given instruction offset.
*/
private void checkMixedStackEntriesBefore(int offset)
{
TracedStack stackBefore = partialEvaluator.getStackBefore(offset);
// Check all stack entries.
int stackSize = stackBefore.size();
for (int stackEntryIndex = 0; stackEntryIndex < stackSize; stackEntryIndex++)
{
// Check reference entries.
Value stackEntry = stackBefore.getBottom(stackEntryIndex);
if (stackEntry.computationalType() == Value.TYPE_REFERENCE)
{
// Check reference entries with multiple producers.
InstructionOffsetValue producerOffsets =
stackBefore.getBottomActualProducerValue(stackEntryIndex).instructionOffsetValue();
int producerCount = producerOffsets.instructionOffsetCount();
if (producerCount > 1)
{
// Is the consumed stack entry not a simple enum?
ReferenceValue consumedStackEntry =
stackEntry.referenceValue();
if (!isSimpleEnumType(consumedStackEntry))
{
// Check all producers.
for (int producerIndex = 0; producerIndex < producerCount; producerIndex++)
{
int producerOffset =
producerOffsets.instructionOffset(producerIndex);
if (producerOffset >= 0)
{
if (DEBUG)
{
ReferenceValue producedValue =
partialEvaluator.getStackAfter(producerOffset).getTop(0).referenceValue();
if (isSimpleEnumType(producedValue))
{
System.out.println("SimpleEnumUseChecker: ["+producedValue.getType()+"] mixed with general type on stack");
}
}
// Make sure the produced stack entry isn't a
// simple enum either.
markPushedComplexEnumType(producerOffset);
}
}
}
}
}
}
}
/**
* Unmarks simple enum classes that are mixed with incompatible reference
* types in the variables before the given instruction offset.
*/
private void checkMixedVariablesBefore(int offset)
{
TracedVariables variablesBefore =
partialEvaluator.getVariablesBefore(offset);
// Check all variables.
int variablesSize = variablesBefore.size();
for (int variableIndex = 0; variableIndex < variablesSize; variableIndex++)
{
// Check reference variables.
Value variable = variablesBefore.getValue(variableIndex);
if (variable != null &&
variable.computationalType() == Value.TYPE_REFERENCE)
{
// Check reference variables with multiple producers.
InstructionOffsetValue producerOffsets =
variablesBefore.getProducerValue(variableIndex).instructionOffsetValue();
int producerCount = producerOffsets.instructionOffsetCount();
if (producerCount > 1)
{
// Is the consumed variable not a simple enum?
ReferenceValue consumedVariable =
variable.referenceValue();
if (!isSimpleEnumType(consumedVariable))
{
// Check all producers.
for (int producerIndex = 0; producerIndex < producerCount; producerIndex++)
{
int producerOffset =
producerOffsets.instructionOffset(producerIndex);
if (producerOffset >= 0)
{
if (DEBUG)
{
ReferenceValue producedValue =
partialEvaluator.getVariablesAfter(producerOffset).getValue(variableIndex).referenceValue();
if (isSimpleEnumType(producedValue))
{
System.out.println("SimpleEnumUseChecker: ["+producedValue.getType()+"] mixed with general type in variables");
}
}
// Make sure the stored variable entry isn't a
// simple enum either.
markStoredComplexEnumType(producerOffset, variableIndex);
}
}
}
}
}
}
}
/**
* Returns whether the instruction at the given offset is popping two
* identical reference types.
*/
private boolean isPoppingIdenticalTypes(int offset,
int stackEntryIndex1,
int stackEntryIndex2)
{
TracedStack stackBefore = partialEvaluator.getStackBefore(offset);
String type1 =
stackBefore.getTop(stackEntryIndex1).referenceValue().getType();
String type2 =
stackBefore.getTop(stackEntryIndex2).referenceValue().getType();
return type1 == null ? type2 == null : type1.equals(type2);
}
/**
* Returns whether the instruction at the given offset is popping exactly
* the reference type of the specified class constant.
*/
private boolean isPoppingExpectedType(int offset,
Clazz clazz,
int constantIndex)
{
return isPoppingExpectedType(offset, 0, clazz, constantIndex);
}
/**
* Returns whether the instruction at the given offset is popping exactly
* the reference type of the specified class constant.
*/
private boolean isPoppingExpectedType(int offset,
int stackEntryIndex,
Clazz clazz,
int constantIndex)
{
return isPoppingExpectedType(offset,
stackEntryIndex,
clazz.getClassName(constantIndex));
}
/**
* Returns whether the instruction at the given offset is popping exactly
* the given reference type.
*/
private boolean isPoppingExpectedType(int offset,
int stackEntryIndex,
String expectedType)
{
TracedStack stackBefore = partialEvaluator.getStackBefore(offset);
String poppedType =
stackBefore.getTop(stackEntryIndex).referenceValue().getType();
return expectedType.equals(poppedType);
}
/**
* Returns whether the given method is returning a simple enum type.
* This includes simple enum arrays.
*/
private boolean isReturningSimpleEnumType(Clazz clazz, Method method)
{
String descriptor = method.getDescriptor(clazz);
String returnType = ClassUtil.internalMethodReturnType(descriptor);
if (ClassUtil.isInternalClassType(returnType))
{
Clazz[] referencedClasses =
((ProgramMethod)method).referencedClasses;
if (referencedClasses != null)
{
int returnedClassIndex =
new DescriptorClassEnumeration(descriptor).classCount() - 1;
Clazz returnedClass = referencedClasses[returnedClassIndex];
return isSimpleEnum(returnedClass);
}
}
return false;
}
/**
* Returns whether the instruction at the given offset is popping a type
* with a simple enum class. This includes simple enum arrays.
*/
private boolean isPoppingSimpleEnumType(int offset)
{
return isPoppingSimpleEnumType(offset, 0);
}
/**
* Returns whether the instruction at the given offset is popping a type
* with a simple enum class. This includes simple enum arrays.
*/
private boolean isPoppingSimpleEnumType(int offset, int stackEntryIndex)
{
ReferenceValue referenceValue =
partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue();
return isSimpleEnumType(referenceValue);
}
/**
* Returns whether the given value is a simple enum type. This includes
* simple enum arrays.
*/
private boolean isSimpleEnumType(ReferenceValue referenceValue)
{
return isSimpleEnum(referenceValue.getReferencedClass());
}
/**
* Returns whether the given class is not null and a simple enum class.
*/
private boolean isSimpleEnum(Clazz clazz)
{
return clazz != null &&
SimpleEnumMarker.isSimpleEnum(clazz);
}
/**
* Marks the enum class of the popped type as complex.
*/
private void markConstantComplexEnumType(Clazz clazz, int constantIndex)
{
clazz.constantPoolEntryAccept(constantIndex,
referencedComplexEnumMarker);
}
/**
* Marks the enum class of the popped type as complex.
*/
private void markPoppedComplexEnumType(int offset)
{
markPoppedComplexEnumType(offset, 0);
}
/**
* Marks the enum class of the specified popped type as complex.
*/
private void markPoppedComplexEnumType(int offset, int stackEntryIndex)
{
ReferenceValue referenceValue =
partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue();
markComplexEnumType(referenceValue);
}
/**
* Marks the enum class of the specified pushed type as complex.
*/
private void markPushedComplexEnumType(int offset)
{
ReferenceValue referenceValue =
partialEvaluator.getStackAfter(offset).getTop(0).referenceValue();
markComplexEnumType(referenceValue);
}
/**
* Marks the enum class of the specified stored type as complex.
*/
private void markStoredComplexEnumType(int offset, int variableIndex)
{
ReferenceValue referenceValue =
partialEvaluator.getVariablesAfter(offset).getValue(variableIndex).referenceValue();
markComplexEnumType(referenceValue);
}
/**
* Marks the enum class of the specified value as complex.
*/
private void markComplexEnumType(ReferenceValue referenceValue)
{
Clazz clazz = referenceValue.getReferencedClass();
if (clazz != null)
{
clazz.accept(complexEnumMarker);
}
}
}