diff options
Diffstat (limited to 'src/proguard/optimize/evaluation')
14 files changed, 3102 insertions, 183 deletions
diff --git a/src/proguard/optimize/evaluation/EvaluationShrinker.java b/src/proguard/optimize/evaluation/EvaluationShrinker.java index 2e86532..daccec1 100644 --- a/src/proguard/optimize/evaluation/EvaluationShrinker.java +++ b/src/proguard/optimize/evaluation/EvaluationShrinker.java @@ -2,7 +2,7 @@ * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * - * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) + * 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 @@ -30,7 +30,7 @@ import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; -import proguard.evaluation.*; +import proguard.evaluation.TracedStack; import proguard.evaluation.value.*; import proguard.optimize.info.*; @@ -50,8 +50,8 @@ implements AttributeVisitor private static final boolean DEBUG_RESULTS = false; private static final boolean DEBUG = false; /*/ - private static boolean DEBUG_RESULTS = true; - private static boolean DEBUG = true; + private static boolean DEBUG = System.getProperty("es") != null; + private static boolean DEBUG_RESULTS = DEBUG; //*/ private static final int UNSUPPORTED = -1; @@ -177,11 +177,7 @@ implements AttributeVisitor if (DEBUG_RESULTS) { System.out.println(); - System.out.println("Class "+ClassUtil.externalClassName(clazz.getName())); - System.out.println("Method "+ClassUtil.externalFullMethodDescription(clazz.getName(), - 0, - method.getName(clazz), - method.getDescriptor(clazz))); + System.out.println("EvaluationShrinker ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]"); } // Initialize the necessary array. @@ -542,7 +538,7 @@ implements AttributeVisitor int parameterSize = ParameterUsageMarker.getParameterSize(programMethod); // Make the method invocation static, if possible. - if ((programMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) == 0 && + if ((programMethod.getAccessFlags() & ClassConstants.ACC_STATIC) == 0 && !ParameterUsageMarker.isParameterUsed(programMethod, 0)) { replaceByStaticInvocation(programClass, @@ -705,7 +701,7 @@ implements AttributeVisitor { // Mark any variable initializations for this variable load that // are required according to the JVM. - markVariableInitializers(offset, variableInstruction.variableIndex); + markVariableInitializersBefore(offset, variableInstruction.variableIndex); } } } @@ -729,6 +725,8 @@ implements AttributeVisitor if (isInstructionNecessary(offset)) { // Check all stack entries that are popped. + // Unusual case: an exception handler with an exception that is + // no longer consumed directly by a method. // Typical case: a freshly marked variable initialization that // requires some value on the stack. int popCount = instruction.stackPopCount(clazz); @@ -739,17 +737,36 @@ implements AttributeVisitor int stackSize = tracedStack.size(); + int requiredPopCount = 0; int requiredPushCount = 0; for (int stackIndex = stackSize - popCount; stackIndex < stackSize; stackIndex++) { - if (!isStackSimplifiedBefore(offset, stackIndex)) + boolean stackSimplifiedBefore = + isStackSimplifiedBefore(offset, stackIndex); + boolean stackEntryPresentBefore = + isStackEntryPresentBefore(offset, stackIndex); + + if (stackSimplifiedBefore) { // Is this stack entry pushed by any producer - // (because it is required by other consumers)? + // (maybe an exception in an exception handler)? if (isStackEntryPresentBefore(offset, stackIndex)) { // Mark all produced stack entries. markStackEntryProducers(offset, stackIndex); + + // Remember to pop it. + requiredPopCount++; + } + } + else + { + // Is this stack entry pushed by any producer + // (because it is required by other consumers)? + if (stackEntryPresentBefore) + { + // Mark all produced stack entries. + markStackEntryProducers(offset, stackIndex); } else { @@ -759,6 +776,14 @@ implements AttributeVisitor } } + // Pop some unnecessary stack entries. + if (requiredPopCount > 0) + { + if (DEBUG) System.out.println(" Inserting before marked consumer "+instruction.toString(offset)); + + insertPopInstructions(offset, false, true, popCount); + } + // Push some necessary stack entries. if (requiredPushCount > 0) { @@ -769,7 +794,7 @@ implements AttributeVisitor throw new IllegalArgumentException("Unsupported stack size increment ["+requiredPushCount+"] at ["+offset+"]"); } - insertPushInstructions(offset, false, tracedStack.getTop(0).computationalType()); + insertPushInstructions(offset, false, true, tracedStack.getTop(0).computationalType()); } } @@ -826,7 +851,7 @@ implements AttributeVisitor { if (DEBUG) System.out.println(" Inserting after marked producer "+instruction.toString(offset)); - insertPopInstructions(offset, false, requiredPopCount); + insertPopInstructions(offset, false, false, requiredPopCount); } } } @@ -863,7 +888,7 @@ implements AttributeVisitor { if (DEBUG) System.out.println(" Replacing unmarked consumer "+instruction.toString(offset)); - insertPopInstructions(offset, true, expectedPopCount); + insertPopInstructions(offset, true, false, expectedPopCount); } } @@ -893,7 +918,7 @@ implements AttributeVisitor { if (DEBUG) System.out.println(" Replacing unmarked producer "+instruction.toString(offset)); - insertPushInstructions(offset, true, tracedStack.getTop(0).computationalType()); + insertPushInstructions(offset, true, false, tracedStack.getTop(0).computationalType()); } } } @@ -1434,35 +1459,65 @@ implements AttributeVisitor /** - * Marks the initializing instructions of the variable consumer at the given - * offset. - * @param consumerOffset the offset of the variable consumer. - * @param variableIndex the index of the variable that is loaded. + * Ensures that the given variable is initialized before the specified + * consumer of that variable, in the JVM's view. + * @param consumerOffset the instruction offset before which the variable + * needs to be initialized. + * @param variableIndex the index of the variable. */ - private void markVariableInitializers(int consumerOffset, - int variableIndex) + private void markVariableInitializersBefore(int consumerOffset, + int variableIndex) { + // Make sure the variable is initialized after all producers. + // Use the simple evaluator, to get the JVM's view of what is + // initialized. InstructionOffsetValue producerOffsets = simplePartialEvaluator.getVariablesBefore(consumerOffset).getProducerValue(variableIndex).instructionOffsetValue(); - if (producerOffsets != null) + int offsetCount = producerOffsets.instructionOffsetCount(); + for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++) { - int offsetCount = producerOffsets.instructionOffsetCount(); - for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++) + // Avoid infinite loops by only looking at producers before + // the consumer. + int producerOffset = + producerOffsets.instructionOffset(offsetIndex); + if (producerOffset < consumerOffset) { - // Make sure the variable and the instruction are marked - // at the producing offset. - int offset = producerOffsets.instructionOffset(offsetIndex); + markVariableInitializersAfter(producerOffset, variableIndex); + } + } + } - if (!isInstructionNecessary(offset) && - isVariableInitialization(offset, variableIndex)) - { - if (DEBUG) System.out.print(" Marking initialization of v"+variableIndex+" at "); - markInstruction(offset); + /** + * Ensures that the given variable is initialized after the specified + * producer of that variable, in the JVM's view. + * @param producerOffset the instruction offset after which the variable + * needs to be initialized. + * @param variableIndex the index of the variable. + */ + private void markVariableInitializersAfter(int producerOffset, + int variableIndex) + { + // No problem if the producer has already been marked. + if (!isInstructionNecessary(producerOffset)) + { + // Is the unmarked producer a variable initialization? + if (isVariableInitialization(producerOffset, variableIndex)) + { + // Mark the producer. + if (DEBUG) System.out.print(" Marking initialization of v"+variableIndex+" at "); - if (DEBUG) System.out.println(); - } + markInstruction(producerOffset); + + if (DEBUG) System.out.println(); + } + else + { + // Don't mark the producer, but recursively look at the + // preceding producers of the same variable. Their values + // will fall through, replacing this producer. + markVariableInitializersBefore(producerOffset, variableIndex); } } } @@ -1645,6 +1700,7 @@ implements AttributeVisitor */ private void insertPushInstructions(int offset, boolean replace, + boolean before, int computationalType) { // Mark this instruction. @@ -1657,21 +1713,7 @@ implements AttributeVisitor if (DEBUG) System.out.println(": "+replacementInstruction.toString(offset)); // Replace or insert the push instruction. - if (replace) - { - // Replace the push instruction. - codeAttributeEditor.replaceInstruction(offset, replacementInstruction); - } - else - { - // Insert the push instruction. - codeAttributeEditor.insertBeforeInstruction(offset, replacementInstruction); - - if (extraAddedInstructionVisitor != null) - { - replacementInstruction.accept(null, null, null, offset, extraAddedInstructionVisitor); - } - } + insertInstruction(offset, replace, before, replacementInstruction); } @@ -1700,7 +1742,10 @@ implements AttributeVisitor * Pops the given number of stack entries at or after the given offset. * The instructions are marked as necessary. */ - private void insertPopInstructions(int offset, boolean replace, int popCount) + private void insertPopInstructions(int offset, + boolean replace, + boolean before, + int popCount) { // Mark this instruction. markInstruction(offset); @@ -1713,19 +1758,7 @@ implements AttributeVisitor Instruction popInstruction = new SimpleInstruction(InstructionConstants.OP_POP); - if (replace) - { - codeAttributeEditor.replaceInstruction(offset, popInstruction); - } - else - { - codeAttributeEditor.insertAfterInstruction(offset, popInstruction); - - if (extraAddedInstructionVisitor != null) - { - popInstruction.accept(null, null, null, offset, extraAddedInstructionVisitor); - } - } + insertInstruction(offset, replace, before, popInstruction); break; } case 2: @@ -1734,19 +1767,7 @@ implements AttributeVisitor Instruction popInstruction = new SimpleInstruction(InstructionConstants.OP_POP2); - if (replace) - { - codeAttributeEditor.replaceInstruction(offset, popInstruction); - } - else - { - codeAttributeEditor.insertAfterInstruction(offset, popInstruction); - - if (extraAddedInstructionVisitor != null) - { - popInstruction.accept(null, null, null, offset, extraAddedInstructionVisitor); - } - } + insertInstruction(offset, replace, before, popInstruction); break; } default: @@ -1771,31 +1792,86 @@ implements AttributeVisitor popInstructions[popCount / 2] = popInstruction; } - if (replace) - { - codeAttributeEditor.replaceInstruction(offset, popInstructions); + insertInstructions(offset, + replace, + before, + popInstruction, + popInstructions); + break; + } + } + } - for (int index = 1; index < popInstructions.length; index++) - { - if (extraAddedInstructionVisitor != null) - { - popInstructions[index].accept(null, null, null, offset, extraAddedInstructionVisitor); - } - } - } - else + + /** + * Inserts or replaces the given instruction at the given offset. + */ + private void insertInstruction(int offset, + boolean replace, + boolean before, + Instruction instruction) + { + if (replace) + { + codeAttributeEditor.replaceInstruction(offset, instruction); + } + else + { + if (before) + { + codeAttributeEditor.insertBeforeInstruction(offset, instruction); + } + else + { + codeAttributeEditor.insertAfterInstruction(offset, instruction); + } + + if (extraAddedInstructionVisitor != null) + { + instruction.accept(null, null, null, offset, extraAddedInstructionVisitor); + } + } + } + + + /** + * Inserts or replaces the given instruction at the given offset. + */ + private void insertInstructions(int offset, + boolean replace, + boolean before, + Instruction instruction, + Instruction[] instructions) + { + if (replace) + { + codeAttributeEditor.replaceInstruction(offset, instructions); + + if (extraAddedInstructionVisitor != null) + { + for (int index = 1; index < instructions.length; index++) { - codeAttributeEditor.insertAfterInstruction(offset, popInstructions); + instructions[index].accept(null, null, null, offset, extraAddedInstructionVisitor); + } + } + } + else + { + if (before) + { + codeAttributeEditor.insertBeforeInstruction(offset, instructions); + } + else + { + codeAttributeEditor.insertAfterInstruction(offset, instructions); + } - for (int index = 0; index < popInstructions.length; index++) - { - if (extraAddedInstructionVisitor != null) - { - popInstructions[index].accept(null, null, null, offset, extraAddedInstructionVisitor); - } - } + for (int index = 0; index < instructions.length; index++) + { + if (extraAddedInstructionVisitor != null) + { + instructions[index].accept(null, null, null, offset, extraAddedInstructionVisitor); } - break; } } } @@ -1998,14 +2074,14 @@ implements AttributeVisitor int variableIndex) { // Wasn't the variable set yet? - Value valueBefore = partialEvaluator.getVariablesBefore(instructionOffset).getValue(variableIndex); + Value valueBefore = simplePartialEvaluator.getVariablesBefore(instructionOffset).getValue(variableIndex); if (valueBefore == null) { return true; } // Is the computational type different now? - Value valueAfter = partialEvaluator.getVariablesAfter(instructionOffset).getValue(variableIndex); + Value valueAfter = simplePartialEvaluator.getVariablesAfter(instructionOffset).getValue(variableIndex); if (valueAfter.computationalType() != valueBefore.computationalType()) { return true; @@ -2020,7 +2096,7 @@ implements AttributeVisitor } // Was the producer an argument (which may be removed)? - Value producersBefore = partialEvaluator.getVariablesBefore(instructionOffset).getProducerValue(variableIndex); + Value producersBefore = simplePartialEvaluator.getVariablesBefore(instructionOffset).getProducerValue(variableIndex); return producersBefore.instructionOffsetValue().instructionOffsetCount() == 1 && producersBefore.instructionOffsetValue().instructionOffset(0) == PartialEvaluator.AT_METHOD_ENTRY; } diff --git a/src/proguard/optimize/evaluation/EvaluationSimplifier.java b/src/proguard/optimize/evaluation/EvaluationSimplifier.java index e6e73d9..8187342 100644 --- a/src/proguard/optimize/evaluation/EvaluationSimplifier.java +++ b/src/proguard/optimize/evaluation/EvaluationSimplifier.java @@ -2,7 +2,7 @@ * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * - * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) + * 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 @@ -27,10 +27,12 @@ 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.*; +import proguard.classfile.visitor.ClassPrinter; +import proguard.evaluation.TracedVariables; import proguard.evaluation.value.*; -import proguard.optimize.info.*; +import proguard.optimize.info.SideEffectInstructionChecker; + +import java.util.Arrays; /** * This AttributeVisitor simplifies the code attributes that it visits, based @@ -49,7 +51,7 @@ implements AttributeVisitor, //* private static final boolean DEBUG = false; /*/ - private static boolean DEBUG = true; + private static boolean DEBUG = System.getProperty("es") != null; //*/ private final InstructionVisitor extraInstructionVisitor; @@ -125,11 +127,7 @@ implements AttributeVisitor, if (DEBUG) { System.out.println(); - System.out.println("Class "+ClassUtil.externalClassName(clazz.getName())); - System.out.println("Method "+ClassUtil.externalFullMethodDescription(clazz.getName(), - 0, - method.getName(clazz), - method.getDescriptor(clazz))); + System.out.println("EvaluationSimplifier ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]"); } // Evaluate the method. @@ -185,6 +183,7 @@ implements AttributeVisitor, case InstructionConstants.OP_I2B: case InstructionConstants.OP_I2C: case InstructionConstants.OP_I2S: + case InstructionConstants.OP_ARRAYLENGTH: replaceIntegerPushInstruction(clazz, offset, simpleInstruction); break; @@ -354,15 +353,50 @@ implements AttributeVisitor, } - public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) + public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction) { // First try to simplify it to a simple branch. - replaceBranchInstruction(clazz, offset, switchInstruction); + replaceBranchInstruction(clazz, offset, tableSwitchInstruction); - // Otherwise make sure all branch targets are valid. + // Otherwise try to simplify simple enum switches. if (!codeAttributeEditor.isModified(offset)) { - replaceSwitchInstruction(clazz, offset, switchInstruction); + replaceSimpleEnumSwitchInstruction(clazz, + codeAttribute, + offset, + tableSwitchInstruction); + + // Otherwise make sure all branch targets are valid. + if (!codeAttributeEditor.isModified(offset)) + { + cleanUpSwitchInstruction(clazz, offset, tableSwitchInstruction); + + trimSwitchInstruction(clazz, offset, tableSwitchInstruction); + } + } + } + + + public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) + { + // First try to simplify it to a simple branch. + replaceBranchInstruction(clazz, offset, lookUpSwitchInstruction); + + // Otherwise try to simplify simple enum switches. + if (!codeAttributeEditor.isModified(offset)) + { + replaceSimpleEnumSwitchInstruction(clazz, + codeAttribute, + offset, + lookUpSwitchInstruction); + + // Otherwise make sure all branch targets are valid. + if (!codeAttributeEditor.isModified(offset)) + { + cleanUpSwitchInstruction(clazz, offset, lookUpSwitchInstruction); + + trimSwitchInstruction(clazz, offset, lookUpSwitchInstruction); + } } } @@ -824,9 +858,185 @@ implements AttributeVisitor, /** + * Replaces the given table switch instruction, if it is based on the value + * of a fixed array. This is typical for switches on simple enums. + */ + private void replaceSimpleEnumSwitchInstruction(Clazz clazz, + CodeAttribute codeAttribute, + int offset, + TableSwitchInstruction tableSwitchInstruction) + { + // Check if the switch instruction is consuming a single value loaded + // from a fully specified array. + InstructionOffsetValue producerOffsets = + partialEvaluator.getStackBefore(offset).getTopProducerValue(0).instructionOffsetValue(); + + if (producerOffsets.instructionOffsetCount() == 1) + { + int producerOffset = producerOffsets.instructionOffset(0); + + if (codeAttribute.code[producerOffset] == InstructionConstants.OP_IALOAD && + !codeAttributeEditor.isModified(producerOffset)) + { + ReferenceValue referenceValue = + partialEvaluator.getStackBefore(producerOffset).getTop(1).referenceValue(); + + if (referenceValue.isParticular()) + { + // Simplify the entire construct. + replaceSimpleEnumSwitchInstruction(clazz, + codeAttribute, + producerOffset, + offset, + tableSwitchInstruction, + referenceValue); + } + } + } + } + + + /** + * Replaces the given table switch instruction that is based on a value of + * the given fixed array. + */ + private void replaceSimpleEnumSwitchInstruction(Clazz clazz, + CodeAttribute codeAttribute, + int loadOffset, + int switchOffset, + TableSwitchInstruction tableSwitchInstruction, + ReferenceValue mappingValue) + { + ValueFactory valueFactory = new ParticularValueFactory(); + + // Transform the jump offsets. + int[] jumpOffsets = tableSwitchInstruction.jumpOffsets; + int[] newJumpOffsets = new int[mappingValue.arrayLength(valueFactory).value()]; + + for (int index = 0; index < newJumpOffsets.length; index++) + { + int switchCase = + mappingValue.integerArrayLoad(valueFactory.createIntegerValue( + index), + valueFactory).value(); + + newJumpOffsets[index] = + switchCase >= tableSwitchInstruction.lowCase && + switchCase <= tableSwitchInstruction.highCase ? + jumpOffsets[switchCase - tableSwitchInstruction.lowCase] : + tableSwitchInstruction.defaultOffset; + } + + // Update the instruction. + tableSwitchInstruction.lowCase = 0; + tableSwitchInstruction.highCase = newJumpOffsets.length - 1; + tableSwitchInstruction.jumpOffsets = newJumpOffsets; + + // Replace the original one with the new version. + replaceSimpleEnumSwitchInstruction(clazz, + loadOffset, + switchOffset, + tableSwitchInstruction); + + cleanUpSwitchInstruction(clazz, switchOffset, tableSwitchInstruction); + + trimSwitchInstruction(clazz, switchOffset, tableSwitchInstruction); + } + + + /** + * Replaces the given look up switch instruction, if it is based on the + * value of a fixed array. This is typical for switches on simple enums. + */ + private void replaceSimpleEnumSwitchInstruction(Clazz clazz, + CodeAttribute codeAttribute, + int offset, + LookUpSwitchInstruction lookupSwitchInstruction) + { + // Check if the switch instruction is consuming a single value loaded + // from a fully specified array. + InstructionOffsetValue producerOffsets = + partialEvaluator.getStackBefore(offset).getTopProducerValue(0).instructionOffsetValue(); + + if (producerOffsets.instructionOffsetCount() == 1) + { + int producerOffset = producerOffsets.instructionOffset(0); + + if (codeAttribute.code[producerOffset] == InstructionConstants.OP_IALOAD && + !codeAttributeEditor.isModified(producerOffset)) + { + ReferenceValue referenceValue = + partialEvaluator.getStackBefore(producerOffset).getTop(1).referenceValue(); + + if (referenceValue.isParticular()) + { + // Simplify the entire construct. + replaceSimpleEnumSwitchInstruction(clazz, + codeAttribute, + producerOffset, + offset, + lookupSwitchInstruction, + referenceValue); + } + } + } + } + + + /** + * Replaces the given look up switch instruction that is based on a value of + * the given fixed array. This is typical for switches on simple enums. + */ + private void replaceSimpleEnumSwitchInstruction(Clazz clazz, + CodeAttribute codeAttribute, + int loadOffset, + int switchOffset, + LookUpSwitchInstruction lookupSwitchInstruction, + ReferenceValue mappingValue) + { + ValueFactory valueFactory = new ParticularValueFactory(); + + // Transform the jump offsets. + int[] cases = lookupSwitchInstruction.cases; + int[] jumpOffsets = lookupSwitchInstruction.jumpOffsets; + int[] newJumpOffsets = new int[mappingValue.arrayLength(valueFactory).value()]; + + for (int index = 0; index < newJumpOffsets.length; index++) + { + int switchCase = + mappingValue.integerArrayLoad(valueFactory.createIntegerValue(index), + valueFactory).value(); + + int caseIndex = Arrays.binarySearch(cases, switchCase); + + newJumpOffsets[index] = caseIndex >= 0 ? + jumpOffsets[caseIndex] : + lookupSwitchInstruction.defaultOffset; + } + + // Replace the original lookup switch with a table switch. + TableSwitchInstruction replacementSwitchInstruction = + new TableSwitchInstruction(InstructionConstants.OP_TABLESWITCH, + lookupSwitchInstruction.defaultOffset, + 0, + newJumpOffsets.length - 1, + newJumpOffsets); + + replaceSimpleEnumSwitchInstruction(clazz, + loadOffset, + switchOffset, + replacementSwitchInstruction); + + cleanUpSwitchInstruction(clazz, switchOffset, replacementSwitchInstruction); + + trimSwitchInstruction(clazz, switchOffset, replacementSwitchInstruction); + } + + + /** * Makes sure all branch targets of the given switch instruction are valid. */ - private void replaceSwitchInstruction(Clazz clazz, + private void cleanUpSwitchInstruction(Clazz clazz, int offset, SwitchInstruction switchInstruction) { @@ -872,6 +1082,129 @@ implements AttributeVisitor, /** + * Trims redundant offsets from the given switch instruction. + */ + private void trimSwitchInstruction(Clazz clazz, + int offset, + TableSwitchInstruction tableSwitchInstruction) + { + // Get an offset that can serve as a valid default offset. + int defaultOffset = tableSwitchInstruction.defaultOffset; + int[] jumpOffsets = tableSwitchInstruction.jumpOffsets; + int length = jumpOffsets.length; + + // Find the lowest index with a non-default jump offset. + int lowIndex = 0; + while (lowIndex < length && + jumpOffsets[lowIndex] == defaultOffset) + { + lowIndex++; + } + + // Find the highest index with a non-default jump offset. + int highIndex = length - 1; + while (highIndex >= 0 && + jumpOffsets[highIndex] == defaultOffset) + { + highIndex--; + } + + // Can we use a shorter array? + int newLength = highIndex - lowIndex + 1; + if (newLength < length) + { + if (newLength <= 0) + { + // Replace the switch instruction by a simple branch instruction. + Instruction replacementInstruction = + new BranchInstruction(InstructionConstants.OP_GOTO, + defaultOffset); + + replaceInstruction(clazz, offset, tableSwitchInstruction, + replacementInstruction); + } + else + { + // Trim the array. + int[] newJumpOffsets = new int[newLength]; + + System.arraycopy(jumpOffsets, lowIndex, newJumpOffsets, 0, newLength); + + tableSwitchInstruction.jumpOffsets = newJumpOffsets; + tableSwitchInstruction.lowCase += lowIndex; + tableSwitchInstruction.highCase -= length - newLength - lowIndex; + + replaceInstruction(clazz, offset, tableSwitchInstruction, + tableSwitchInstruction); + } + } + } + + + /** + * Trims redundant offsets from the given switch instruction. + */ + private void trimSwitchInstruction(Clazz clazz, + int offset, + LookUpSwitchInstruction lookUpSwitchInstruction) + { + // Get an offset that can serve as a valid default offset. + int defaultOffset = lookUpSwitchInstruction.defaultOffset; + int[] jumpOffsets = lookUpSwitchInstruction.jumpOffsets; + int length = jumpOffsets.length; + int newLength = length; + + // Count the default jump offsets. + for (int index = 0; index < length; index++) + { + if (jumpOffsets[index] == defaultOffset) + { + newLength--; + } + } + + // Can we use shorter arrays? + if (newLength < length) + { + if (newLength <= 0) + { + // Replace the switch instruction by a simple branch instruction. + Instruction replacementInstruction = + new BranchInstruction(InstructionConstants.OP_GOTO, + defaultOffset); + + replaceInstruction(clazz, offset, lookUpSwitchInstruction, + replacementInstruction); + } + else + { + // Remove redundant entries from the arrays. + int[] cases = lookUpSwitchInstruction.cases; + int[] newJumpOffsets = new int[newLength]; + int[] newCases = new int[newLength]; + + int newIndex = 0; + + for (int index = 0; index < length; index++) + { + if (jumpOffsets[index] != defaultOffset) + { + newJumpOffsets[newIndex] = jumpOffsets[index]; + newCases[newIndex++] = cases[index]; + } + } + + lookUpSwitchInstruction.jumpOffsets = newJumpOffsets; + lookUpSwitchInstruction.cases = newCases; + + replaceInstruction(clazz, offset, lookUpSwitchInstruction, + lookUpSwitchInstruction); + } + } + } + + + /** * Replaces the given instruction by an infinite loop. */ private void replaceByInfiniteLoop(Clazz clazz, @@ -891,7 +1224,11 @@ implements AttributeVisitor, { // Note: we're not passing the right arguments for now, knowing that // they aren't used anyway. - instruction.accept(clazz, null, null, offset, extraInstructionVisitor); + instruction.accept(clazz, + null, + null, + offset, + extraInstructionVisitor); } } @@ -985,4 +1322,39 @@ implements AttributeVisitor, } } } + + + /** + * Replaces the simple enum switch instructions at a given offsets by a + * given replacement instruction. + */ + private void replaceSimpleEnumSwitchInstruction(Clazz clazz, + int loadOffset, + int switchOffset, + SwitchInstruction replacementSwitchInstruction) + { + if (DEBUG) System.out.println(" Replacing switch instruction at ["+switchOffset+"] -> ["+loadOffset+"] swap + pop, "+replacementSwitchInstruction.toString(switchOffset)+")"); + + // Remove the array load instruction. + codeAttributeEditor.replaceInstruction(loadOffset, new Instruction[] + { + new SimpleInstruction(InstructionConstants.OP_SWAP), + new SimpleInstruction(InstructionConstants.OP_POP), + }); + + // Replace the switch instruction. + codeAttributeEditor.replaceInstruction(switchOffset, replacementSwitchInstruction); + + // 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. + replacementSwitchInstruction.accept(clazz, + null, + null, + switchOffset, + extraInstructionVisitor); + } + } } diff --git a/src/proguard/optimize/evaluation/LivenessAnalyzer.java b/src/proguard/optimize/evaluation/LivenessAnalyzer.java index 5ce8ccb..87bd4e5 100644 --- a/src/proguard/optimize/evaluation/LivenessAnalyzer.java +++ b/src/proguard/optimize/evaluation/LivenessAnalyzer.java @@ -2,7 +2,7 @@ * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * - * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) + * 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 diff --git a/src/proguard/optimize/evaluation/LoadingInvocationUnit.java b/src/proguard/optimize/evaluation/LoadingInvocationUnit.java index d6baa67..80b4b84 100644 --- a/src/proguard/optimize/evaluation/LoadingInvocationUnit.java +++ b/src/proguard/optimize/evaluation/LoadingInvocationUnit.java @@ -2,7 +2,7 @@ * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * - * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) + * 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 @@ -26,7 +26,7 @@ import proguard.evaluation.BasicInvocationUnit; import proguard.evaluation.value.*; /** - * This InvocationUbit loads parameter values and return values that were + * This InvocationUnit loads parameter values and return values that were * previously stored with the methods that are invoked. * * @see StoringInvocationUnit @@ -45,7 +45,7 @@ extends BasicInvocationUnit */ public LoadingInvocationUnit(ValueFactory valueFactory) { - this(valueFactory, false, false, false); + this(valueFactory, true, true, true); } @@ -80,8 +80,7 @@ extends BasicInvocationUnit { // Retrieve the stored field class value. ReferenceValue value = StoringInvocationUnit.getFieldClassValue((Field)referencedMember); - if (value != null && - value.isParticular()) + if (value != null) { return value; } @@ -104,8 +103,7 @@ extends BasicInvocationUnit { // Retrieve the stored field value. Value value = StoringInvocationUnit.getFieldValue((Field)referencedMember); - if (value != null && - value.isParticular()) + if (value != null) { return value; } @@ -126,8 +124,7 @@ extends BasicInvocationUnit { // Retrieve the stored method parameter value. Value value = StoringInvocationUnit.getMethodParameterValue(method, parameterIndex); - if (value != null && - value.isParticular()) + if (value != null) { return value; } @@ -153,8 +150,7 @@ extends BasicInvocationUnit { // Retrieve the stored method return value. Value value = StoringInvocationUnit.getMethodReturnValue((Method)referencedMember); - if (value != null && - value.isParticular()) + if (value != null) { return value; } @@ -165,31 +161,4 @@ extends BasicInvocationUnit refConstant, type); } - - -// // Small utility methods. -// -// private Value refresh(Value value) -// { -// if (value.isParticular()) -// { -// return value; -// } -// -// switch (value.computationalType()) -// { -// case Value.TYPE_INTEGER: return valueFactory.createIntegerValue(); -// case Value.TYPE_LONG: return valueFactory.createLongValue(); -// case Value.TYPE_FLOAT: return valueFactory.createFloatValue(); -// case Value.TYPE_DOUBLE: return valueFactory.createDoubleValue(); -// default: -// { -// ReferenceValue referenceValue = value.referenceValue(); -// -// return valueFactory.createReferenceValue(referenceValue.getType(), -// referenceValue.getReferencedClass(), -// referenceValue.isNull() != Value.NEVER); -// } -// } -// } } diff --git a/src/proguard/optimize/evaluation/PartialEvaluator.java b/src/proguard/optimize/evaluation/PartialEvaluator.java index 6a5bedf..0301f12 100644 --- a/src/proguard/optimize/evaluation/PartialEvaluator.java +++ b/src/proguard/optimize/evaluation/PartialEvaluator.java @@ -2,7 +2,7 @@ * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * - * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) + * 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 @@ -96,9 +96,10 @@ implements AttributeVisitor, * during evaluation. * @param invocationUnit the invocation unit that will handle all * communication with other fields and methods. - * @param evaluateAllCode a flag that specifies whether all branch targets - * and exception handlers should be evaluated, - * even if they are unreachable. + * @param evaluateAllCode a flag that specifies whether all casts, branch + * targets, and exception handlers should be + * evaluated, even if they are unnecessary or + * unreachable. */ public PartialEvaluator(ValueFactory valueFactory, InvocationUnit invocationUnit, @@ -132,17 +133,22 @@ implements AttributeVisitor, /** * Creates a new PartialEvaluator. - * @param valueFactory the value factory that will create all - * values during evaluation. - * @param invocationUnit the invocation unit that will handle all - * communication with other fields and methods. - * @param evaluateAllCode a flag that specifies whether all branch - * targets and exception handlers should be - * evaluated, even if they are unreachable. - * @param branchUnit the branch unit that will handle all - * branches. - * @param branchTargetFinder the utility class that will find all - * branches. + * @param valueFactory the value factory that will create + * all values during evaluation. + * @param invocationUnit the invocation unit that will handle + * all communication with other fields + * and methods. + * @param evaluateAllCode a flag that specifies whether all + * casts, branch targets, and exception + * handlers should be evaluated, even + * if they are unnecessary or + * unreachable. + * @param branchUnit the branch unit that will handle all + * branches. + * @param branchTargetFinder the utility class that will find all + * branches. + * @param callingInstructionBlockStack the stack of instruction blocks to + * be evaluated */ private PartialEvaluator(ValueFactory valueFactory, InvocationUnit invocationUnit, @@ -293,7 +299,7 @@ implements AttributeVisitor, if (isTraced(offset)) { int initializationOffset = branchTargetFinder.initializationOffset(offset); - if (initializationOffset != NONE) + if (initializationOffset >= 0) { System.out.println(" is to be initialized at ["+initializationOffset+"]"); } @@ -639,7 +645,8 @@ implements AttributeVisitor, stack, valueFactory, branchUnit, - invocationUnit); + invocationUnit, + evaluateAllCode); int instructionOffset = startOffset; @@ -1055,7 +1062,7 @@ implements AttributeVisitor, //stack.push(valueFactory.createReference((ClassConstant)((ProgramClass)clazz).getConstant(exceptionInfo.u2catchType), false)); String catchClassName = catchType != 0 ? clazz.getClassName(catchType) : - ClassConstants.INTERNAL_NAME_JAVA_LANG_THROWABLE; + ClassConstants.NAME_JAVA_LANG_THROWABLE; Clazz catchClass = catchType != 0 ? ((ClassConstant)((ProgramClass)clazz).getConstant(catchType)).referencedClass : diff --git a/src/proguard/optimize/evaluation/SimpleEnumArrayPropagator.java b/src/proguard/optimize/evaluation/SimpleEnumArrayPropagator.java new file mode 100644 index 0000000..d6aaf7d --- /dev/null +++ b/src/proguard/optimize/evaluation/SimpleEnumArrayPropagator.java @@ -0,0 +1,94 @@ +/* + * 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.util.SimplifiedVisitor; +import proguard.classfile.visitor.*; +import proguard.evaluation.value.*; +import proguard.optimize.info.*; + +/** + * This ClassVisitor propagates the value of the $VALUES field to the values() + * method in the simple enum classes that it visits. + * + * @see SimpleEnumMarker + * @author Eric Lafortune + */ +public class SimpleEnumArrayPropagator +extends SimplifiedVisitor +implements ClassVisitor, + MemberVisitor +{ + private final ValueFactory valueFactory = new ParticularValueFactory(); + + private Value array; + + + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + // Update the return value of the "int[] values()" method. + programClass.methodsAccept(new MemberDescriptorFilter("()[I", this)); + } + + + // Implementations for MemberVisitor. + + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) + { + // Find the array length of the "int[] $VALUES" field. + programClass.fieldsAccept(new MemberDescriptorFilter("[I", this)); + + if (array != null) + { + // Set the array value with the found array length. We can't use + // the original array, because its elements might get overwritten. + Value propagatedArray = + valueFactory.createArrayReferenceValue("I", + null, + array.referenceValue().arrayLength( + valueFactory)); + + setMethodReturnValue(programMethod, propagatedArray); + + array = null; + } + } + + public void visitProgramField(ProgramClass programClass, ProgramField programField) + { + array = StoringInvocationUnit.getFieldValue(programField); + } + + + // Small utility methods. + + private static void setMethodReturnValue(Method method, Value value) + { + MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method); + if (info != null) + { + info.setReturnValue(value); + } + } +} diff --git a/src/proguard/optimize/evaluation/SimpleEnumClassChecker.java b/src/proguard/optimize/evaluation/SimpleEnumClassChecker.java new file mode 100644 index 0000000..1bd5008 --- /dev/null +++ b/src/proguard/optimize/evaluation/SimpleEnumClassChecker.java @@ -0,0 +1,74 @@ +/* + * 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.visitor.*; +import proguard.optimize.info.SimpleEnumMarker; + +/** + * This ClassVisitor marks all program classes that it visits as simple enums, + * if their methods qualify. + * + * @author Eric Lafortune + */ +public class SimpleEnumClassChecker +implements ClassVisitor +{ + //* + private static final boolean DEBUG = false; + /*/ + private static boolean DEBUG = System.getProperty("enum") != null; + //*/ + + + private final SimpleEnumMarker simpleEnumMarker = new SimpleEnumMarker(true); + private final MemberVisitor virtualMethodChecker = new MemberAccessFilter(0, + ClassConstants.ACC_PRIVATE | + ClassConstants.ACC_STATIC, + new MemberToClassVisitor( + new SimpleEnumMarker(false))); + + + // Implementations for ClassVisitor. + + public void visitLibraryClass(LibraryClass libraryClass) {} + + public void visitProgramClass(ProgramClass programClass) + { + // Does the class have the simple enum constructor? + if (programClass.findMethod(ClassConstants.METHOD_NAME_INIT, + ClassConstants.METHOD_TYPE_INIT_ENUM) != null) + { + if (DEBUG) + { + System.out.println("SimpleEnumClassChecker: ["+programClass.getName()+"] is a candidate simple enum, without extra fields"); + } + + // Mark it. + simpleEnumMarker.visitProgramClass(programClass); + + // However, unmark it again if it has any non-private, non-static + // methods. + programClass.methodsAccept(virtualMethodChecker); + } + } +}
\ No newline at end of file diff --git a/src/proguard/optimize/evaluation/SimpleEnumClassSimplifier.java b/src/proguard/optimize/evaluation/SimpleEnumClassSimplifier.java new file mode 100644 index 0000000..33f775f --- /dev/null +++ b/src/proguard/optimize/evaluation/SimpleEnumClassSimplifier.java @@ -0,0 +1,164 @@ +/* + * 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.*; +import proguard.classfile.editor.*; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.SimplifiedVisitor; +import proguard.classfile.visitor.*; +import proguard.optimize.info.SimpleEnumMarker; +import proguard.optimize.peephole.*; + +/** + * This ClassVisitor simplifies the classes that it visits to simple enums. + * + * @see SimpleEnumMarker + * @see MemberReferenceFixer + * @author Eric Lafortune + */ +public class SimpleEnumClassSimplifier +extends SimplifiedVisitor +implements ClassVisitor, + AttributeVisitor, + InstructionVisitor +{ + //* + private static final boolean DEBUG = false; + /*/ + private static boolean DEBUG = System.getProperty("enum") != null; + //*/ + + + private static final int ENUM_CLASS_NAME = InstructionSequenceReplacer.A; + private static final int ENUM_TYPE_NAME = InstructionSequenceReplacer.B; + private static final int ENUM_CONSTANT_NAME = InstructionSequenceReplacer.X; + private static final int ENUM_CONSTANT_ORDINAL = InstructionSequenceReplacer.Y; + private static final int ENUM_CONSTANT_FIELD_NAME = InstructionSequenceReplacer.Z; + + private static final int STRING_ENUM_CONSTANT_NAME = 0; + + private static final int METHOD_ENUM_INIT = 1; + private static final int FIELD_ENUM_CONSTANT = 2; + + private static final int CLASS_ENUM = 3; + + private static final int NAME_AND_TYPE_ENUM_INIT = 4; + private static final int NAME_AND_TYPE_ENUM_CONSTANT = 5; + + private static final int UTF8_INIT = 6; + private static final int UTF8_STRING_I = 7; + + + private static final Constant[] CONSTANTS = new Constant[] + { + new StringConstant(ENUM_CONSTANT_NAME, null, null), + + new MethodrefConstant(CLASS_ENUM, NAME_AND_TYPE_ENUM_INIT, null, null), + new FieldrefConstant( CLASS_ENUM, NAME_AND_TYPE_ENUM_CONSTANT, null, null), + + new ClassConstant(ENUM_CLASS_NAME, null), + + new NameAndTypeConstant(UTF8_INIT, UTF8_STRING_I), + new NameAndTypeConstant(ENUM_CONSTANT_FIELD_NAME, ENUM_TYPE_NAME), + + new Utf8Constant(ClassConstants.METHOD_NAME_INIT), + new Utf8Constant(ClassConstants.METHOD_TYPE_INIT_ENUM), + }; + + private static final Instruction[] INSTRUCTIONS = new Instruction[] + { + new ConstantInstruction(InstructionConstants.OP_NEW, CLASS_ENUM), + new SimpleInstruction(InstructionConstants.OP_DUP), + new ConstantInstruction(InstructionConstants.OP_LDC, STRING_ENUM_CONSTANT_NAME), + new SimpleInstruction(InstructionConstants.OP_ICONST_0, ENUM_CONSTANT_ORDINAL), + new ConstantInstruction(InstructionConstants.OP_INVOKESPECIAL, METHOD_ENUM_INIT), + }; + + private static final Instruction[] REPLACEMENT_INSTRUCTIONS = new Instruction[] + { + new SimpleInstruction(InstructionConstants.OP_SIPUSH, ENUM_CONSTANT_ORDINAL), + new SimpleInstruction(InstructionConstants.OP_ICONST_1), + new SimpleInstruction(InstructionConstants.OP_IADD), + }; + + + private final CodeAttributeEditor codeAttributeEditor = + new CodeAttributeEditor(true, true); + + private final InstructionSequenceReplacer instructionSequenceReplacer = + new InstructionSequenceReplacer(CONSTANTS, + INSTRUCTIONS, + REPLACEMENT_INSTRUCTIONS, + null, + codeAttributeEditor); + + private final MemberVisitor initializerSimplifier = new AllAttributeVisitor(this); + + + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + if (DEBUG) + { + System.out.println("SimpleEnumClassSimplifier: ["+programClass.getName()+"]"); + } + + // Unmark the class as an enum. + programClass.u2accessFlags &= ~ClassConstants.ACC_ENUM; + + // Remove the valueOf method, if present. + Method valueOfMethod = + programClass.findMethod(ClassConstants.METHOD_NAME_VALUEOF, null); + if (valueOfMethod != null) + { + new ClassEditor(programClass).removeMethod(valueOfMethod); + } + + // Simplify the static initializer. + programClass.methodAccept(ClassConstants.METHOD_NAME_CLINIT, + ClassConstants.METHOD_TYPE_CLINIT, + initializerSimplifier); + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Set up the code attribute editor. + codeAttributeEditor.reset(codeAttribute.u4codeLength); + + // Find the peephole changes. + codeAttribute.instructionsAccept(clazz, method, instructionSequenceReplacer); + + // Apply the peephole changes. + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + } +} diff --git a/src/proguard/optimize/evaluation/SimpleEnumDescriptorSimplifier.java b/src/proguard/optimize/evaluation/SimpleEnumDescriptorSimplifier.java new file mode 100644 index 0000000..f1323ea --- /dev/null +++ b/src/proguard/optimize/evaluation/SimpleEnumDescriptorSimplifier.java @@ -0,0 +1,583 @@ +/* + * 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.*; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.*; +import proguard.classfile.visitor.*; +import proguard.optimize.info.*; + +/** + * This ClassVisitor simplifies the descriptors that contain simple enums in + * the program classes that it visits. + * + * @see SimpleEnumMarker + * @see MemberReferenceFixer + * @author Eric Lafortune + */ +public class SimpleEnumDescriptorSimplifier +extends SimplifiedVisitor +implements ClassVisitor, + ConstantVisitor, + MemberVisitor, + AttributeVisitor, + LocalVariableInfoVisitor, + LocalVariableTypeInfoVisitor +{ + //* + private static final boolean DEBUG = false; + /*/ + private static boolean DEBUG = System.getProperty("enum") != null; + //*/ + + + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + if (DEBUG) + { + System.out.println("SimpleEnumDescriptorSimplifier: "+programClass.getName()); + } + + // Simplify the class members. + programClass.fieldsAccept(this); + programClass.methodsAccept(this); + + // Simplify the attributes. + //programClass.attributesAccept(this); + + // Simplify the simple enum array constants. + programClass.constantPoolEntriesAccept(this); + } + + + // 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? + Clazz referencedClass = stringConstant.referencedClass; + if (isSimpleEnum(referencedClass)) + { + // Is it an array type? + String name = stringConstant.getString(clazz); + if (ClassUtil.isInternalArrayType(name)) + { + // Update the type. + ConstantPoolEditor constantPoolEditor = + new ConstantPoolEditor((ProgramClass)clazz); + + String newName = simplifyDescriptor(name, referencedClass); + + stringConstant.u2stringIndex = + constantPoolEditor.addUtf8Constant(newName); + + // Clear the referenced class. + stringConstant.referencedClass = null; + } + } + } + + + public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) + { + // Update the descriptor if it has any simple enum classes. + String descriptor = invokeDynamicConstant.getType(clazz); + String newDescriptor = simplifyDescriptor(descriptor, invokeDynamicConstant.referencedClasses); + + if (!descriptor.equals(newDescriptor)) + { + // Update the descriptor. + ConstantPoolEditor constantPoolEditor = + new ConstantPoolEditor((ProgramClass)clazz); + + invokeDynamicConstant.u2nameAndTypeIndex = + constantPoolEditor.addNameAndTypeConstant(invokeDynamicConstant.getName(clazz), + newDescriptor); + + // Update the referenced classes. + invokeDynamicConstant.referencedClasses = + simplifyReferencedClasses(descriptor, invokeDynamicConstant.referencedClasses); + } + } + + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + // Does the constant refer to a simple enum type? + Clazz referencedClass = classConstant.referencedClass; + if (isSimpleEnum(referencedClass)) + { + // Is it an array type? + String name = classConstant.getName(clazz); + if (ClassUtil.isInternalArrayType(name)) + { + // Update the type. + ConstantPoolEditor constantPoolEditor = + new ConstantPoolEditor((ProgramClass)clazz); + + String newName = simplifyDescriptor(name, referencedClass); + + classConstant.u2nameIndex = + constantPoolEditor.addUtf8Constant(newName); + + // Clear the referenced class. + classConstant.referencedClass = null; + } + } + } + + + // Implementations for MemberVisitor. + + public void visitProgramField(ProgramClass programClass, ProgramField programField) + { + // Update the descriptor if it has a simple enum class. + String descriptor = programField.getDescriptor(programClass); + String newDescriptor = simplifyDescriptor(descriptor, programField.referencedClass); + + if (!descriptor.equals(newDescriptor)) + { + String name = programField.getName(programClass); + String newName = name + ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); + + if (DEBUG) + { + System.out.println("SimpleEnumDescriptorSimplifier: ["+programClass.getName()+"."+name+" "+descriptor + "] -> ["+newName+" "+newDescriptor+"]"); + } + + ConstantPoolEditor constantPoolEditor = + new ConstantPoolEditor(programClass); + + // Update the name. + programField.u2nameIndex = + constantPoolEditor.addUtf8Constant(newName); + + // Update the descriptor itself. + programField.u2descriptorIndex = + constantPoolEditor.addUtf8Constant(newDescriptor); + + // Clear the referenced class. + programField.referencedClass = null; + + // Clear the field value. + FieldOptimizationInfo fieldOptimizationInfo = + FieldOptimizationInfo.getFieldOptimizationInfo(programField); + if (fieldOptimizationInfo != null) + { + fieldOptimizationInfo.resetValue(programClass, programField); + } + + // Simplify the signature. + programField.attributesAccept(programClass, this); + } + } + + + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) + { +// // Skip the valueOf method. +// if (programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_VALUEOF)) +// { +// return; +// } + + // Simplify the code, the signature, and the parameter annotations, + // before simplifying the descriptor. + programMethod.attributesAccept(programClass, this); + + // Update the descriptor if it has any simple enum classes. + String descriptor = programMethod.getDescriptor(programClass); + String newDescriptor = simplifyDescriptor(descriptor, programMethod.referencedClasses); + + if (!descriptor.equals(newDescriptor)) + { + String name = programMethod.getName(programClass); + String newName = name; + + // Append a code, if the method isn't a class instance initializer. + if (!name.equals(ClassConstants.METHOD_NAME_INIT)) + { + newName += ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); + } + + if (DEBUG) + { + System.out.println("SimpleEnumDescriptorSimplifier: ["+programClass.getName()+"."+name+descriptor+"] -> ["+newName+newDescriptor+"]"); + } + + ConstantPoolEditor constantPoolEditor = + new ConstantPoolEditor(programClass); + + // Update the name, if necessary. + if (!newName.equals(name)) + { + programMethod.u2nameIndex = + constantPoolEditor.addUtf8Constant(newName); + } + + // Update the descriptor itself. + programMethod.u2descriptorIndex = + constantPoolEditor.addUtf8Constant(newDescriptor); + + // Update the referenced classes. + programMethod.referencedClasses = + simplifyReferencedClasses(descriptor, programMethod.referencedClasses); + } + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Simplify the local variable descriptors. + codeAttribute.attributesAccept(clazz, method, this); + } + + + public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) + { + // Change the references of the local variables. + localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + } + + + public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) + { + // Change the references of the local variables. + localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + } + + + public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute signatureAttribute) + { + // We're only looking at the base type for now. + if (signatureAttribute.referencedClasses != null && + signatureAttribute.referencedClasses.length > 0) + { + // Update the signature if it has any simple enum classes. + String signature = signatureAttribute.getSignature(clazz); + String newSignature = simplifyDescriptor(signature, + signatureAttribute.referencedClasses[0]); + + if (!signature.equals(newSignature)) + { + // Update the signature. + signatureAttribute.u2signatureIndex = + new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); + + // Clear the referenced class. + signatureAttribute.referencedClasses[0] = null; + } + } + } + + + public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute signatureAttribute) + { + // Compute the new signature. + String signature = signatureAttribute.getSignature(clazz); + String newSignature = simplifyDescriptor(signature, + signatureAttribute.referencedClasses); + + if (!signature.equals(newSignature)) + { + // Update the signature. + signatureAttribute.u2signatureIndex = + new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); + } + } + + + // Implementations for LocalVariableInfoVisitor. + + public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) + { + // Update the descriptor if it has a simple enum class. + String descriptor = localVariableInfo.getDescriptor(clazz); + String newDescriptor = simplifyDescriptor(descriptor, localVariableInfo.referencedClass); + + if (!descriptor.equals(newDescriptor)) + { + // Update the descriptor. + localVariableInfo.u2descriptorIndex = + new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor); + + // Clear the referenced class. + localVariableInfo.referencedClass = null; + } + } + + + // Implementations for LocalVariableTypeInfoVisitor. + + public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) + { + // We're only looking at the base type for now. + if (localVariableTypeInfo.referencedClasses != null && + localVariableTypeInfo.referencedClasses.length > 0) + { + // Update the signature if it has any simple enum classes. + String signature = localVariableTypeInfo.getSignature(clazz); + String newSignature = simplifyDescriptor(signature, + localVariableTypeInfo.referencedClasses[0]); + + if (!signature.equals(newSignature)) + { + // Update the signature. + localVariableTypeInfo.u2signatureIndex = + new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); + + // Clear the referenced class. + localVariableTypeInfo.referencedClasses[0] = null; + } + } + } + + + // Small utility methods. + + /** + * Returns the descriptor with simplified enum type. + */ + private String simplifyDescriptor(String descriptor, + Clazz referencedClass) + { + return isSimpleEnum(referencedClass) ? + descriptor.substring(0, ClassUtil.internalArrayTypeDimensionCount(descriptor)) + ClassConstants.TYPE_INT : + descriptor; + } + + + /** + * Returns the descriptor with simplified enum types. + */ + private String simplifyDescriptor(String descriptor, + Clazz[] referencedClasses) + { + if (referencedClasses != null) + { + InternalTypeEnumeration internalTypeEnumeration = + new InternalTypeEnumeration(descriptor); + + int referencedClassIndex = 0; + + StringBuffer newDescriptorBuffer = + new StringBuffer(descriptor.length()); + + // Go over the formal type parameters. + { + // Consider the classes referenced by this formal type + // parameter. + String type = internalTypeEnumeration.formalTypeParameters(); + int count = new DescriptorClassEnumeration(type).classCount(); + + // Replace simple enum types. + for (int counter = 0; counter < count; counter++) + { + Clazz referencedClass = + referencedClasses[referencedClassIndex++]; + + if (isSimpleEnum(referencedClass)) + { + // Let's replace the simple enum type by + // java.lang.Integer. + type = + type.substring(0, ClassUtil.internalArrayTypeDimensionCount(type)) + + ClassConstants.NAME_JAVA_LANG_INTEGER; + } + } + + newDescriptorBuffer.append(type); + } + + newDescriptorBuffer.append(ClassConstants.METHOD_ARGUMENTS_OPEN); + + // Go over the parameters. + while (internalTypeEnumeration.hasMoreTypes()) + { + // Consider the classes referenced by this parameter type. + String type = internalTypeEnumeration.nextType(); + int count = new DescriptorClassEnumeration(type).classCount(); + + // Replace simple enum types. + for (int counter = 0; counter < count; counter++) + { + Clazz referencedClass = + referencedClasses[referencedClassIndex++]; + + if (isSimpleEnum(referencedClass)) + { + type = + type.substring(0, ClassUtil.internalArrayTypeDimensionCount(type)) + + ClassConstants.TYPE_INT; + } + } + + newDescriptorBuffer.append(type); + } + + newDescriptorBuffer.append(ClassConstants.METHOD_ARGUMENTS_CLOSE); + + // Go over the return value. + { + String type = internalTypeEnumeration.returnType(); + int count = new DescriptorClassEnumeration(type).classCount(); + + // Replace simple enum types. + for (int counter = 0; counter < count; counter++) + { + Clazz referencedClass = + referencedClasses[referencedClassIndex++]; + + if (isSimpleEnum(referencedClass)) + { + type = + type.substring(0, ClassUtil.internalArrayTypeDimensionCount(type)) + + ClassConstants.TYPE_INT; + } + } + + newDescriptorBuffer.append(type); + } + + descriptor = newDescriptorBuffer.toString(); + } + + return descriptor; + } + + + /** + * Returns the simplified and shrunk array of referenced classes for the + * given descriptor. + */ + private Clazz[] simplifyReferencedClasses(String descriptor, + Clazz[] referencedClasses) + { + if (referencedClasses != null) + { + InternalTypeEnumeration internalTypeEnumeration = + new InternalTypeEnumeration(descriptor); + + int referencedClassIndex = 0; + int newReferencedClassIndex = 0; + + // Go over the formal type parameters. + { + String type = internalTypeEnumeration.formalTypeParameters(); + int count = new DescriptorClassEnumeration(type).classCount(); + + // Clear all non-simple enum classes + // (now java.lang.Integer). + for (int counter = 0; counter < count; counter++) + { + Clazz referencedClass = + referencedClasses[referencedClassIndex++]; + + referencedClasses[newReferencedClassIndex++] = + isSimpleEnum(referencedClass) ? null : referencedClass; + } + } + + // Go over the parameters. + while (internalTypeEnumeration.hasMoreTypes()) + { + // Consider the classes referenced by this parameter type. + String type = internalTypeEnumeration.nextType(); + int count = new DescriptorClassEnumeration(type).classCount(); + + // Copy all non-simple enum classes. + for (int counter = 0; counter < count; counter++) + { + Clazz referencedClass = + referencedClasses[referencedClassIndex++]; + + if (!isSimpleEnum(referencedClass)) + { + referencedClasses[newReferencedClassIndex++] = + referencedClass; + } + } + } + + // Go over the return type. + { + String type = internalTypeEnumeration.returnType(); + int count = new DescriptorClassEnumeration(type).classCount(); + + // Copy all non-simple enum classes. + for (int counter = 0; counter < count; counter++) + { + Clazz referencedClass = + referencedClasses[referencedClassIndex++]; + + if (!isSimpleEnum(referencedClass)) + { + referencedClasses[newReferencedClassIndex++] = + referencedClass; + } + } + } + + // Shrink the array to the proper size. + if (newReferencedClassIndex == 0) + { + referencedClasses = null; + } + else if (newReferencedClassIndex < referencedClassIndex) + { + Clazz[] newReferencedClasses = new Clazz[newReferencedClassIndex]; + System.arraycopy(referencedClasses, 0, + newReferencedClasses, 0, + newReferencedClassIndex); + + referencedClasses = newReferencedClasses; + } + } + + return referencedClasses; + } + + + /** + * Returns whether the given class is not null and a simple enum class. + */ + private boolean isSimpleEnum(Clazz clazz) + { + return clazz != null && + SimpleEnumMarker.isSimpleEnum(clazz); + } +} diff --git a/src/proguard/optimize/evaluation/SimpleEnumUseChecker.java b/src/proguard/optimize/evaluation/SimpleEnumUseChecker.java new file mode 100644 index 0000000..b748c68 --- /dev/null +++ b/src/proguard/optimize/evaluation/SimpleEnumUseChecker.java @@ -0,0 +1,760 @@ +/* + * 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); + } + } +} 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); + } + } + } +} diff --git a/src/proguard/optimize/evaluation/StoringInvocationUnit.java b/src/proguard/optimize/evaluation/StoringInvocationUnit.java index 846f685..271b654 100644 --- a/src/proguard/optimize/evaluation/StoringInvocationUnit.java +++ b/src/proguard/optimize/evaluation/StoringInvocationUnit.java @@ -2,7 +2,7 @@ * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * - * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) + * 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 @@ -27,7 +27,7 @@ import proguard.evaluation.value.*; import proguard.optimize.info.*; /** - * This InvocationUbit stores parameter values and return values with the + * This InvocationUnit stores parameter values and return values with the * methods that are invoked. * * @see LoadingInvocationUnit @@ -126,7 +126,7 @@ extends BasicInvocationUnit generalizeMethodReturnValue(method, value); } } - + // Small utility methods. diff --git a/src/proguard/optimize/evaluation/TracedBranchUnit.java b/src/proguard/optimize/evaluation/TracedBranchUnit.java index e6acf6f..9e55275 100644 --- a/src/proguard/optimize/evaluation/TracedBranchUnit.java +++ b/src/proguard/optimize/evaluation/TracedBranchUnit.java @@ -2,7 +2,7 @@ * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * - * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) + * 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 diff --git a/src/proguard/optimize/evaluation/VariableOptimizer.java b/src/proguard/optimize/evaluation/VariableOptimizer.java index 73efddc..bef1445 100644 --- a/src/proguard/optimize/evaluation/VariableOptimizer.java +++ b/src/proguard/optimize/evaluation/VariableOptimizer.java @@ -2,7 +2,7 @@ * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * - * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) + * 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 @@ -21,11 +21,11 @@ package proguard.optimize.evaluation; import proguard.classfile.*; +import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.editor.*; -import proguard.classfile.visitor.MemberVisitor; -import proguard.classfile.attribute.*; import proguard.classfile.util.*; +import proguard.classfile.visitor.MemberVisitor; /** * This AttributeVisitor optimizes variable allocation based on their the liveness, @@ -110,7 +110,7 @@ implements AttributeVisitor, codeAttribute.attributesAccept(clazz, method, this); int startIndex = - (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0 || + (method.getAccessFlags() & ClassConstants.ACC_STATIC) != 0 || reuseThis ? 0 : 1; int parameterSize = |