diff options
Diffstat (limited to 'src/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java')
-rw-r--r-- | src/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/src/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java b/src/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java new file mode 100644 index 0000000..b5a2396 --- /dev/null +++ b/src/proguard/optimize/evaluation/SimpleEnumUseSimplifier.java @@ -0,0 +1,820 @@ +/* + * 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); + } + } + } +} |