blob: b5a2396c75b4c754dfd8dc610a83d74cd4d56a52 [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.AttributeVisitor;
import proguard.classfile.constant.*;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.editor.*;
import proguard.classfile.instruction.*;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import proguard.evaluation.value.*;
import proguard.optimize.info.SimpleEnumMarker;
/**
* This AttributeVisitor simplifies the use of enums in the code attributes that
* it visits.
*
* @see SimpleEnumMarker
* @see MemberReferenceFixer
* @author Eric Lafortune
*/
public class SimpleEnumUseSimplifier
extends SimplifiedVisitor
implements AttributeVisitor,
InstructionVisitor,
ConstantVisitor,
ParameterVisitor
{
//*
private static final boolean DEBUG = false;
/*/
private static boolean DEBUG = System.getProperty("enum") != null;
//*/
private final InstructionVisitor extraInstructionVisitor;
private final PartialEvaluator partialEvaluator;
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(true, true);
private final ConstantVisitor nullParameterFixer = new ReferencedMemberVisitor(new AllParameterVisitor(this));
// Fields acting as parameters and return values for the visitor methods.
private Clazz invocationClazz;
private Method invocationMethod;
private CodeAttribute invocationCodeAttribute;
private int invocationOffset;
private boolean isSimpleEnum;
/**
* Creates a new SimpleEnumUseSimplifier.
*/
public SimpleEnumUseSimplifier()
{
this(new PartialEvaluator(), null);
}
/**
* Creates a new SimpleEnumDescriptorSimplifier.
* @param partialEvaluator the partial evaluator that will
* execute the code and provide
* information about the results.
* @param extraInstructionVisitor an optional extra visitor for all
* simplified instructions.
*/
public SimpleEnumUseSimplifier(PartialEvaluator partialEvaluator,
InstructionVisitor extraInstructionVisitor)
{
this.partialEvaluator = partialEvaluator;
this.extraInstructionVisitor = extraInstructionVisitor;
}
// Implementations for AttributeVisitor.
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
if (DEBUG)
{
System.out.println("SimpleEnumUseSimplifier: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
}
// Skip the non-static methods of simple enum classes.
if (SimpleEnumMarker.isSimpleEnum(clazz) &&
(method.getAccessFlags() & ClassConstants.ACC_STATIC) == 0)
{
return;
}
// Evaluate the method.
partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
int codeLength = codeAttribute.u4codeLength;
// Reset the code changes.
codeAttributeEditor.reset(codeLength);
// Replace any instructions that can be simplified.
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);
}
}
// Apply all accumulated changes to the code.
codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
}
// Implementations for InstructionVisitor.
public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
{
switch (simpleInstruction.opcode)
{
case InstructionConstants.OP_AALOAD:
{
if (isPushingSimpleEnum(offset))
{
// Load a simple enum integer from an integer array.
replaceInstruction(clazz,
offset,
simpleInstruction,
new SimpleInstruction(
InstructionConstants.OP_IALOAD));
}
break;
}
case InstructionConstants.OP_AASTORE:
{
if (isPoppingSimpleEnumArray(offset, 2))
{
// Store a simple enum integer in an integer array.
replaceInstruction(clazz,
offset,
simpleInstruction,
new SimpleInstruction(InstructionConstants.OP_IASTORE));
// Replace any producers of null constants.
replaceNullStackEntryProducers(clazz, method, codeAttribute, offset);
}
break;
}
case InstructionConstants.OP_ARETURN:
{
if (isReturningSimpleEnum(clazz, method))
{
// Return a simple enum integer instead of an enum.
replaceInstruction(clazz,
offset,
simpleInstruction,
new SimpleInstruction(InstructionConstants.OP_IRETURN));
// Replace any producers of null constants.
replaceNullStackEntryProducers(clazz, method, codeAttribute, offset);
}
break;
}
}
}
public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
{
int variableIndex = variableInstruction.variableIndex;
switch (variableInstruction.opcode)
{
case InstructionConstants.OP_ALOAD:
case InstructionConstants.OP_ALOAD_0:
case InstructionConstants.OP_ALOAD_1:
case InstructionConstants.OP_ALOAD_2:
case InstructionConstants.OP_ALOAD_3:
{
if (isPushingSimpleEnum(offset))
{
// Load a simple enum integer instead of an enum.
replaceInstruction(clazz,
offset,
variableInstruction,
new VariableInstruction(InstructionConstants.OP_ILOAD,
variableIndex));
// Replace any producers of null constants.
replaceNullVariableProducers(clazz,
method,
codeAttribute,
offset,
variableIndex);
}
break;
}
case InstructionConstants.OP_ASTORE:
case InstructionConstants.OP_ASTORE_0:
case InstructionConstants.OP_ASTORE_1:
case InstructionConstants.OP_ASTORE_2:
case InstructionConstants.OP_ASTORE_3:
{
if (!partialEvaluator.isSubroutineStart(offset) &&
isPoppingSimpleEnum(offset))
{
// Store a simple enum integer instead of an enum.
replaceInstruction(clazz,
offset,
variableInstruction,
new VariableInstruction(InstructionConstants.OP_ISTORE,
variableIndex));
// Replace any producers of null constants.
replaceNullStackEntryProducers(clazz, method, codeAttribute, offset);
}
break;
}
}
}
public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
{
switch (constantInstruction.opcode)
{
case InstructionConstants.OP_PUTSTATIC:
case InstructionConstants.OP_PUTFIELD:
{
// Replace any producers of null constants.
invocationClazz = clazz;
invocationMethod = method;
invocationCodeAttribute = codeAttribute;
invocationOffset = offset;
clazz.constantPoolEntryAccept(constantInstruction.constantIndex,
nullParameterFixer);
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 (isPoppingSimpleEnum(offset, stackEntryIndex))
{
replaceSupportedMethod(clazz,
offset,
constantInstruction,
invokedMethodName,
invokedMethodType);
}
// Fall through to check the parameters.
}
case InstructionConstants.OP_INVOKESPECIAL:
case InstructionConstants.OP_INVOKESTATIC:
case InstructionConstants.OP_INVOKEINTERFACE:
{
// Replace any producers of null constants.
invocationClazz = clazz;
invocationMethod = method;
invocationCodeAttribute = codeAttribute;
invocationOffset = offset;
clazz.constantPoolEntryAccept(constantInstruction.constantIndex,
nullParameterFixer);
break;
}
case InstructionConstants.OP_ANEWARRAY:
{
int constantIndex = constantInstruction.constantIndex;
if (isReferencingSimpleEnum(clazz, constantIndex) &&
!ClassUtil.isInternalArrayType(clazz.getClassName(constantIndex)))
{
// Create an integer array instead of an enum array.
replaceInstruction(clazz,
offset,
constantInstruction,
new SimpleInstruction(InstructionConstants.OP_NEWARRAY,
InstructionConstants.ARRAY_T_INT));
}
break;
}
case InstructionConstants.OP_CHECKCAST:
{
if (isPoppingSimpleEnum(offset))
{
// Enum classes can only be simple if the checkcast
// succeeds, so we can delete it.
deleteInstruction(clazz,
offset,
constantInstruction);
// Replace any producers of null constants.
replaceNullStackEntryProducers(clazz, method, codeAttribute, offset);
}
break;
}
case InstructionConstants.OP_INSTANCEOF:
{
if (isPoppingSimpleEnum(offset))
{
// Enum classes can only be simple if the instanceof
// succeeds, so we can push a constant result.
replaceInstruction(clazz,
offset,
constantInstruction,
new SimpleInstruction(InstructionConstants.OP_ICONST_1));
// Replace any producers of null constants.
replaceNullStackEntryProducers(clazz, method, codeAttribute, offset);
}
break;
}
}
}
public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
{
switch (branchInstruction.opcode)
{
case InstructionConstants.OP_IFACMPEQ:
{
if (isPoppingSimpleEnum(offset))
{
// Compare simple enum integers instead of enums.
replaceInstruction(clazz,
offset,
branchInstruction,
new BranchInstruction(InstructionConstants.OP_IFICMPEQ,
branchInstruction.branchOffset));
}
break;
}
case InstructionConstants.OP_IFACMPNE:
{
if (isPoppingSimpleEnum(offset))
{
// Compare simple enum integers instead of enums.
replaceInstruction(clazz,
offset,
branchInstruction,
new BranchInstruction(InstructionConstants.OP_IFICMPNE,
branchInstruction.branchOffset));
}
break;
}
case InstructionConstants.OP_IFNULL:
{
if (isPoppingSimpleEnum(offset))
{
// Compare with 0 instead of null.
replaceInstruction(clazz,
offset,
branchInstruction,
new BranchInstruction(
InstructionConstants.OP_IFEQ,
branchInstruction.branchOffset));
}
break;
}
case InstructionConstants.OP_IFNONNULL:
{
if (isPoppingSimpleEnum(offset))
{
// Compare with 0 instead of null.
replaceInstruction(clazz,
offset,
branchInstruction,
new BranchInstruction(InstructionConstants.OP_IFNE,
branchInstruction.branchOffset));
}
break;
}
}
}
public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
{
}
// Implementations for ConstantVisitor.
public void visitAnyConstant(Clazz clazz, Constant constant) {}
public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
{
// Does the constant refer to a simple enum type?
isSimpleEnum = isSimpleEnum(stringConstant.referencedClass);
}
public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
{
// Does the constant refer to a simple enum type?
isSimpleEnum = isSimpleEnum(classConstant.referencedClass);
}
// 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.
if (!ClassUtil.isInternalPrimitiveType(parameterType.charAt(0)) &&
isSimpleEnum(referencedClass))
{
// Replace any producers of null constants for this parameter.
int stackEntryIndex = parameterSize - parameterOffset - 1;
replaceNullStackEntryProducers(invocationClazz,
invocationMethod,
invocationCodeAttribute,
invocationOffset,
stackEntryIndex);
}
}
// Small utility methods.
/**
* Returns whether the constant at the given offset is referencing a
* simple enum class.
*/
private boolean isReferencingSimpleEnum(Clazz clazz, int constantIndex)
{
isSimpleEnum = false;
clazz.constantPoolEntryAccept(constantIndex, this);
return isSimpleEnum;
}
/**
* Returns whether the given method is returning a simple enum class.
*/
private boolean isReturningSimpleEnum(Clazz clazz, Method method)
{
String descriptor = method.getDescriptor(clazz);
String returnType = ClassUtil.internalMethodReturnType(descriptor);
if (ClassUtil.isInternalClassType(returnType) &&
!ClassUtil.isInternalArrayType(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 pushing a simple
* enum class.
*/
private boolean isPushingSimpleEnum(int offset)
{
ReferenceValue referenceValue =
partialEvaluator.getStackAfter(offset).getTop(0).referenceValue();
Clazz referencedClass = referenceValue.getReferencedClass();
return isSimpleEnum(referencedClass) &&
!ClassUtil.isInternalArrayType(referenceValue.getType());
}
/**
* Returns whether the instruction at the given offset is popping a simple
* enum class.
*/
private boolean isPoppingSimpleEnum(int offset)
{
return isPoppingSimpleEnum(offset, 0);
}
/**
* Returns whether the instruction at the given offset is popping a simple
* enum class.
*/
private boolean isPoppingSimpleEnum(int offset, int stackEntryIndex)
{
ReferenceValue referenceValue =
partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue();
return isSimpleEnum(referenceValue.getReferencedClass()) &&
!ClassUtil.isInternalArrayType(referenceValue.getType());
}
/**
* Returns whether the instruction at the given offset is popping a simple
* enum type. This includes simple enum arrays.
*/
private boolean isPoppingSimpleEnumType(int offset, int stackEntryIndex)
{
ReferenceValue referenceValue =
partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue();
return isSimpleEnum(referenceValue.getReferencedClass());
}
/**
* Returns whether the instruction at the given offset is popping a
* one-dimensional simple enum array.
*/
private boolean isPoppingSimpleEnumArray(int offset, int stackEntryIndex)
{
ReferenceValue referenceValue =
partialEvaluator.getStackBefore(offset).getTop(stackEntryIndex).referenceValue();
return isSimpleEnum(referenceValue.getReferencedClass()) &&
ClassUtil.internalArrayTypeDimensionCount(referenceValue.getType()) == 1;
}
/**
* Returns whether the given class is not null and a simple enum class.
*/
private boolean isSimpleEnum(Clazz clazz)
{
return clazz != null &&
SimpleEnumMarker.isSimpleEnum(clazz);
}
/**
* Returns whether the specified enum method is supported for simple enums.
*/
private void replaceSupportedMethod(Clazz clazz,
int offset,
Instruction instruction,
String name,
String type)
{
if (name.equals(ClassConstants.METHOD_NAME_ORDINAL) &&
type.equals(ClassConstants.METHOD_TYPE_ORDINAL))
{
Instruction[] replacementInstructions = new Instruction[]
{
new SimpleInstruction(InstructionConstants.OP_ICONST_1),
new SimpleInstruction(InstructionConstants.OP_ISUB),
};
replaceInstructions(clazz,
offset,
instruction,
replacementInstructions);
}
}
/**
* Replaces the instruction at the given offset by the given instructions.
*/
private void replaceInstructions(Clazz clazz,
int offset,
Instruction instruction,
Instruction[] replacementInstructions)
{
if (DEBUG) System.out.println(" Replacing instruction "+instruction.toString(offset)+" -> "+replacementInstructions.length+" instructions");
codeAttributeEditor.replaceInstruction(offset, replacementInstructions);
// Visit the instruction, if required.
if (extraInstructionVisitor != null)
{
// Note: we're not passing the right arguments for now, knowing that
// they aren't used anyway.
instruction.accept(clazz, null, null, offset, extraInstructionVisitor);
}
}
/**
* Replaces the instruction at the given offset by the given instruction,
* popping any now unused stack entries.
*/
private void replaceInstruction(Clazz clazz,
int offset,
Instruction instruction,
Instruction replacementInstruction)
{
// Pop unneeded stack entries if necessary.
int popCount =
instruction.stackPopCount(clazz) -
replacementInstruction.stackPopCount(clazz);
insertPopInstructions(offset, popCount);
if (DEBUG) System.out.println(" Replacing instruction "+instruction.toString(offset)+" -> "+replacementInstruction.toString()+(popCount == 0 ? "" : " ("+popCount+" pops)"));
codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
// Visit the instruction, if required.
if (extraInstructionVisitor != null)
{
// Note: we're not passing the right arguments for now, knowing that
// they aren't used anyway.
instruction.accept(clazz, null, null, offset, extraInstructionVisitor);
}
}
/**
* Deletes the instruction at the given offset, popping any now unused
* stack entries.
*/
private void deleteInstruction(Clazz clazz,
int offset,
Instruction instruction)
{
// Pop unneeded stack entries if necessary.
//int popCount = instruction.stackPopCount(clazz);
//
//insertPopInstructions(offset, popCount);
//
//if (DEBUG) System.out.println(" Deleting instruction "+instruction.toString(offset)+(popCount == 0 ? "" : " ("+popCount+" pops)"));
if (DEBUG) System.out.println(" Deleting instruction "+instruction.toString(offset));
codeAttributeEditor.deleteInstruction(offset);
// Visit the instruction, if required.
if (extraInstructionVisitor != null)
{
// Note: we're not passing the right arguments for now, knowing that
// they aren't used anyway.
instruction.accept(clazz, null, null, offset, extraInstructionVisitor);
}
}
/**
* Pops the given number of stack entries before the instruction at the
* given offset.
*/
private void insertPopInstructions(int offset, int popCount)
{
switch (popCount)
{
case 0:
{
break;
}
case 1:
{
// Insert a single pop instruction.
Instruction popInstruction =
new SimpleInstruction(InstructionConstants.OP_POP);
codeAttributeEditor.insertBeforeInstruction(offset,
popInstruction);
break;
}
case 2:
{
// Insert a single pop2 instruction.
Instruction popInstruction =
new SimpleInstruction(InstructionConstants.OP_POP2);
codeAttributeEditor.insertBeforeInstruction(offset,
popInstruction);
break;
}
default:
{
// Insert the specified number of pop instructions.
Instruction[] popInstructions =
new Instruction[popCount / 2 + popCount % 2];
Instruction popInstruction =
new SimpleInstruction(InstructionConstants.OP_POP2);
for (int index = 0; index < popCount / 2; index++)
{
popInstructions[index] = popInstruction;
}
if (popCount % 2 == 1)
{
popInstruction =
new SimpleInstruction(InstructionConstants.OP_POP);
popInstructions[popCount / 2] = popInstruction;
}
codeAttributeEditor.insertBeforeInstruction(offset,
popInstructions);
break;
}
}
}
/**
* Replaces aconst_null producers of the consumer of the top stack entry
* at the given offset by iconst_0.
*/
private void replaceNullStackEntryProducers(Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int consumerOffset)
{
replaceNullStackEntryProducers(clazz, method, codeAttribute, consumerOffset, 0);
}
/**
* Replaces aconst_null producers of the specified stack entry by
* iconst_0.
*/
private void replaceNullStackEntryProducers(Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int consumerOffset,
int stackEntryIndex)
{
InstructionOffsetValue producerOffsets =
partialEvaluator.getStackBefore(consumerOffset).getTopActualProducerValue(stackEntryIndex).instructionOffsetValue();
for (int index = 0; index < producerOffsets.instructionOffsetCount(); index++)
{
int producerOffset = producerOffsets.instructionOffset(index);
// TODO: A method might be pushing the null constant.
if (producerOffset >= 0 &&
codeAttribute.code[producerOffset] == InstructionConstants.OP_ACONST_NULL)
{
// Replace pushing null by pushing 0.
replaceInstruction(clazz,
producerOffset,
new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
new SimpleInstruction(InstructionConstants.OP_ICONST_0));
}
}
}
/**
* Replaces aconst_null/astore producers of the specified reference variable by
* iconst_0/istore.
*/
private void replaceNullVariableProducers(Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int consumerOffset,
int variableIndex)
{
InstructionOffsetValue producerOffsets =
partialEvaluator.getVariablesBefore(consumerOffset).getProducerValue(variableIndex).instructionOffsetValue();
for (int index = 0; index < producerOffsets.instructionOffsetCount(); index++)
{
int producerOffset = producerOffsets.instructionOffset(index);
if (producerOffset >= 0 &&
partialEvaluator.getVariablesAfter(producerOffset).getValue(variableIndex).referenceValue().isNull() == Value.ALWAYS)
{
// Replace loading null by loading 0.
replaceInstruction(clazz,
producerOffset,
new VariableInstruction(InstructionConstants.OP_ASTORE, variableIndex),
new VariableInstruction(InstructionConstants.OP_ISTORE, variableIndex));
// Replace pushing null by pushing 0.
replaceNullStackEntryProducers(clazz, method, codeAttribute, producerOffset);
}
}
}
}