/* * 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); } } } }