blob: fa60b9acdbde39f813aee60fd425215ec52a0b36 [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2009 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.preverify;
import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.preverification.*;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.editor.*;
import proguard.classfile.instruction.*;
import proguard.classfile.util.SimplifiedVisitor;
import proguard.classfile.visitor.*;
import proguard.evaluation.*;
import proguard.evaluation.value.*;
import proguard.optimize.evaluation.*;
import java.util.*;
/**
* This class can preverify methods in program class pools, according to a given
* specification.
*
* @author Eric Lafortune
*/
public class CodePreverifier
extends SimplifiedVisitor
implements MemberVisitor,
AttributeVisitor
{
//*
private static final boolean DEBUG = false;
/*/
private static boolean DEBUG = true;
//*/
private final boolean microEdition;
private final PartialEvaluator partialEvaluator = new PartialEvaluator();
private final LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer(partialEvaluator);
/**
* Creates a new CodePreverifier.
*/
public CodePreverifier(boolean microEdition)
{
this.microEdition = microEdition;
}
// Implementations for AttributeVisitor.
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
// TODO: Remove this when the preverifier has stabilized.
// Catch any unexpected exceptions from the actual visiting method.
try
{
// Process the code.
visitCodeAttribute0(clazz, method, codeAttribute);
}
catch (RuntimeException ex)
{
System.err.println("Unexpected error while preverifying:");
System.err.println(" Class = ["+clazz.getName()+"]");
System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
throw ex;
}
}
public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
// DEBUG =
// clazz.getName().equals("abc/Def") &&
// method.getName(clazz).equals("abc");
ProgramClass programClass = (ProgramClass)clazz;
ProgramMethod programMethod = (ProgramMethod)method;
// Evaluate the method.
//partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
livenessAnalyzer.visitCodeAttribute(clazz, method, codeAttribute);
// Collect the stack map frames.
List stackMapFrameList = new ArrayList();
for (int offset = 0; offset < codeAttribute.u4codeLength; offset++)
{
// Only store frames at the beginning of code blocks.
if (partialEvaluator.isTraced(offset) &&
partialEvaluator.isBranchOrExceptionTarget(offset))
{
// Convert the variable values to types.
VerificationType[] variableTypes =
correspondingVerificationTypes(programClass,
programMethod,
codeAttribute,
offset,
partialEvaluator.getVariablesBefore(offset));
// Convert the stack values to types.
VerificationType[] stackTypes =
correspondingVerificationTypes(programClass,
programMethod,
codeAttribute,
offset,
partialEvaluator.getStackBefore(offset));
// Create and store a new frame.
stackMapFrameList.add(new FullFrame(offset,
variableTypes,
stackTypes));
}
}
// Compress the stack map frames if the target is not Java Micro Edition.
if (!microEdition && !stackMapFrameList.isEmpty())
{
// Convert the initial variable values to types.
VerificationType[] initialVariables =
correspondingVerificationTypes(programClass,
programMethod,
codeAttribute,
PartialEvaluator.AT_METHOD_ENTRY,
partialEvaluator.getVariablesBefore(0));
// Special case: the <init> method.
if (method.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
{
initialVariables[0] = VerificationTypeFactory.createUninitializedThisType();
}
compressStackMapFrames(initialVariables,
stackMapFrameList);
}
// Get the proper name for the attribute to be added/replaced/deleted.
String stackMapAttributeName = microEdition ?
ClassConstants.ATTR_StackMap :
ClassConstants.ATTR_StackMapTable;
int frameCount = stackMapFrameList.size();
if (DEBUG)
{
Attribute originalStackMapAttribute = codeAttribute.getAttribute(clazz,
stackMapAttributeName);
if (originalStackMapAttribute != null)
{
int originalFrameCount = microEdition ?
((StackMapAttribute)originalStackMapAttribute).u2stackMapFramesCount :
((StackMapTableAttribute)originalStackMapAttribute).u2stackMapFramesCount;
StackMapFrame[] originalFrames = microEdition ?
((StackMapAttribute)originalStackMapAttribute).stackMapFrames :
((StackMapTableAttribute)originalStackMapAttribute).stackMapFrames;
if (frameCount != originalFrameCount ||
!Arrays.equals(stackMapFrameList.toArray(), originalFrames))
{
System.out.println("Original preverification ["+clazz.getName()+"]:");
new ClassPrinter().visitProgramMethod(programClass, programMethod);
}
}
else if (frameCount != 0)
{
System.out.println("Original preverification empty ["+clazz.getName()+"."+method.getName(clazz)+"]");
}
}
if (frameCount == 0)
{
// Remove any stack map (table) attribute from the code attribute.
new AttributesEditor(programClass, programMethod, codeAttribute, true).deleteAttribute(stackMapAttributeName);
}
else
{
Attribute stackMapAttribute;
// Create the appropriate attribute.
if (microEdition)
{
// Copy the frames into an array.
FullFrame[] stackMapFrames = new FullFrame[frameCount];
stackMapFrameList.toArray(stackMapFrames);
// Put the frames into a stack map attribute.
stackMapAttribute = new StackMapAttribute(stackMapFrames);
}
else
{
// Copy the frames into an array.
StackMapFrame[] stackMapFrames = new StackMapFrame[frameCount];
stackMapFrameList.toArray(stackMapFrames);
// Put the frames into a stack map table attribute.
stackMapAttribute = new StackMapTableAttribute(stackMapFrames);
}
// Fill out the name of the stack map attribute.
stackMapAttribute.u2attributeNameIndex =
new ConstantPoolEditor(programClass).addUtf8Constant(stackMapAttributeName);
// Add the new stack map (table) attribute to the code attribute.
new AttributesEditor(programClass, programMethod, codeAttribute, true).addAttribute(stackMapAttribute);
if (DEBUG)
{
System.out.println("Preverifier ["+programClass.getName()+"."+programMethod.getName(programClass)+"]:");
stackMapAttribute.accept(programClass, programMethod, codeAttribute, new ClassPrinter());
}
}
}
// Small utility methods.
/**
* Creates and returns the verification types corresponding to the given
* variables. If necessary, class constants are added to the constant pool
* of the given class.
*/
private VerificationType[] correspondingVerificationTypes(ProgramClass programClass,
ProgramMethod programMethod,
CodeAttribute codeAttribute,
int offset,
TracedVariables variables)
{
int maximumVariablesSize = variables.size();
int typeCount = 0;
int typeIndex = 0;
// Count the the number of verification types, ignoring any nulls at
// the end.
for (int index = 0; index < maximumVariablesSize; index++)
{
Value value = variables.getValue(index);
typeIndex++;
// Remember the maximum live type index.
if (value != null &&
(offset == PartialEvaluator.AT_METHOD_ENTRY ||
livenessAnalyzer.isAliveBefore(offset, index)))
{
typeCount = typeIndex;
// Category 2 types that are alive are stored as single entries.
if (value.isCategory2())
{
index++;
}
}
}
// Create and fill out the verification types.
VerificationType[] types = new VerificationType[typeCount];
typeIndex = 0;
// Note the slightly different terminating condition, because the
// types may have been truncated.
for (int index = 0; typeIndex < typeCount; index++)
{
Value value = variables.getValue(index);
Value producerValue = variables.getProducerValue(index);
// Fill out the type.
VerificationType type;
if (value != null &&
(offset == PartialEvaluator.AT_METHOD_ENTRY ||
livenessAnalyzer.isAliveBefore(offset, index)))
{
type = correspondingVerificationType(programClass,
programMethod,
codeAttribute,
offset,
index == 0,
value,
producerValue);
// Category 2 types that are alive are stored as single entries.
if (value.isCategory2())
{
index++;
}
}
else
{
type = VerificationTypeFactory.createTopType();
}
types[typeIndex++] = type;
}
return types;
}
/**
* Creates and returns the verification types corresponding to the given
* stack. If necessary, class constants are added to the constant pool
* of the given class.
*/
private VerificationType[] correspondingVerificationTypes(ProgramClass programClass,
ProgramMethod programMethod,
CodeAttribute codeAttribute,
int offset,
TracedStack stack)
{
int maximumStackSize = stack.size();
int typeCount = 0;
// Count the the number of verification types.
for (int index = 0; index < maximumStackSize; index++)
{
// We have to work down from the top of the stack.
Value value = stack.getTop(index);
typeCount++;
// Category 2 types are stored as single entries.
if (value.isCategory2())
{
index++;
}
}
// Create and fill out the verification types.
VerificationType[] types = new VerificationType[typeCount];
int typeIndex = typeCount;
for (int index = 0; index < maximumStackSize; index++)
{
// We have to work down from the top of the stack.
Value value = stack.getTop(index);
Value producerValue = stack.getTopProducerValue(index);
// Fill out the type.
types[--typeIndex] =
correspondingVerificationType(programClass,
programMethod,
codeAttribute,
offset,
false,
value,
producerValue);
// Category 2 types are stored as single entries.
if (value.isCategory2())
{
index++;
}
}
return types;
}
/**
* Creates and returns the verification type corresponding to the given
* value. If necessary, a class constant is added to the constant pool of
* the given class.
*/
private VerificationType correspondingVerificationType(ProgramClass programClass,
ProgramMethod programMethod,
CodeAttribute codeAttribute,
int offset,
boolean isVariable0,
Value value,
Value producerValue)
{
if (value == null)
{
return VerificationTypeFactory.createTopType();
}
int type = value.computationalType();
switch (type)
{
case Value.TYPE_INSTRUCTION_OFFSET:
case Value.TYPE_INTEGER: return VerificationTypeFactory.createIntegerType();
case Value.TYPE_LONG: return VerificationTypeFactory.createLongType();
case Value.TYPE_FLOAT: return VerificationTypeFactory.createFloatType();
case Value.TYPE_DOUBLE: return VerificationTypeFactory.createDoubleType();
case Value.TYPE_TOP: return VerificationTypeFactory.createTopType();
case Value.TYPE_REFERENCE:
// Is it a Null type?
ReferenceValue referenceValue = value.referenceValue();
if (referenceValue.isNull() == Value.ALWAYS)
{
return VerificationTypeFactory.createNullType();
}
// Does the reference type have a single producer?
if (offset != PartialEvaluator.AT_METHOD_ENTRY)
{
InstructionOffsetValue producers = producerValue.instructionOffsetValue();
if (producers.instructionOffsetCount() == 1)
{
int producerOffset = producers.instructionOffset(0);
// Follow any dup or swap instructions.
while (producerOffset != PartialEvaluator.AT_METHOD_ENTRY &&
isDupOrSwap(codeAttribute.code[producerOffset]))
{
producers = partialEvaluator.getStackBefore(producerOffset).getTopProducerValue(0).instructionOffsetValue();
producerOffset = producers.instructionOffset(0);
}
// Are we in an instance initialization method,
// before the super initialization, loading "this"?
if (partialEvaluator.isInitializer() &&
offset <= partialEvaluator.superInitializationOffset() &&
(isVariable0 ||
producerOffset > PartialEvaluator.AT_METHOD_ENTRY &&
codeAttribute.code[producerOffset] == InstructionConstants.OP_ALOAD_0))
{
// It's an UninitializedThis type.
return VerificationTypeFactory.createUninitializedThisType();
}
// Is the reference type newly created and still
// uninitialized?
if (producerOffset > PartialEvaluator.AT_METHOD_ENTRY &&
offset <= partialEvaluator.initializationOffset(producerOffset))
{
// It's an Uninitialized type.
return VerificationTypeFactory.createUninitializedType(producerOffset);
}
}
}
// It's an ordinary Object type.
return VerificationTypeFactory.createObjectType(createClassConstant(programClass, referenceValue));
}
throw new IllegalArgumentException("Unknown computational type ["+type+"]");
}
/**
* Finds or creates a class constant for the given reference value, and
* returns its index in the constant pool.
*/
private int createClassConstant(ProgramClass programClass,
ReferenceValue referenceValue)
{
return new ConstantPoolEditor(programClass).addClassConstant(referenceValue.getType(),
referenceValue.getReferencedClass());
}
/**
* Compresses the given list of full frames, for use in a stack map table.
*/
private void compressStackMapFrames(VerificationType[] initialVariableTypes,
List stackMapFrameList)
{
int previousVariablesCount = initialVariableTypes.length;
VerificationType[] previousVariableTypes = initialVariableTypes;
int previousOffset = -1;
for (int index = 0; index < stackMapFrameList.size(); index++)
{
FullFrame fullFrame = (FullFrame)stackMapFrameList.get(index);
int variablesCount = fullFrame.variablesCount;
VerificationType[] variables = fullFrame.variables;
int stackCount = fullFrame.stackCount;
VerificationType[] stack = fullFrame.stack;
// Start computing the compressed frame.
// The default is the full frame.
StackMapFrame compressedFrame = fullFrame;
// Are all variables equal?
if (variablesCount == previousVariablesCount &&
equalVerificationTypes(variables, previousVariableTypes, variablesCount))
{
// Are the stacks equal?
//if (stackCount == previousStackCount &&
// equalVerificationTypes(stack, previousStack, stackCount))
//{
// // Remove the identical frame.
// stackMapFrameList.remove(index--);
//
// // Move on to the next frame (at the same index).
// continue;
//}
// Is the new stack empty?
//else
if (stackCount == 0)
{
compressedFrame = new SameZeroFrame();
}
// Does the new stack contain a single element?
else if (stackCount == 1)
{
compressedFrame = new SameOneFrame(stack[0]);
}
}
// Is the stack empty?
else if (stackCount == 0)
{
int additionalVariablesCount = variablesCount - previousVariablesCount;
// Are the variables chopped?
if (additionalVariablesCount < 0 &&
additionalVariablesCount > -4 &&
equalVerificationTypes(variables, previousVariableTypes, variablesCount))
{
compressedFrame = new LessZeroFrame((byte)-additionalVariablesCount);
}
// Are the variables extended?
else if (//previousVariablesCount > 0 &&
additionalVariablesCount > 0 &&
additionalVariablesCount < 4 &&
equalVerificationTypes(variables, previousVariableTypes, previousVariablesCount))
{
// Copy the additional variables into an array.
VerificationType[] additionalVariables = new VerificationType[additionalVariablesCount];
System.arraycopy(variables, variablesCount - additionalVariablesCount,
additionalVariables, 0,
additionalVariablesCount);
compressedFrame = new MoreZeroFrame(additionalVariables);
}
}
// Compress the instruction offset.
int offset = fullFrame.u2offsetDelta;
compressedFrame.u2offsetDelta = offset - previousOffset - 1;
previousOffset = offset;
// Remember this frame.
previousVariablesCount = fullFrame.variablesCount;
previousVariableTypes = fullFrame.variables;
// Replace the full frame.
stackMapFrameList.set(index, compressedFrame);
}
}
/**
* Returns whether the given arrays of verification types are equal, up to
* the given length.
*/
private boolean equalVerificationTypes(VerificationType[] types1,
VerificationType[] types2,
int length)
{
if (length > 0 &&
(types1.length < length ||
types2.length < length))
{
return false;
}
for (int index = 0; index < length; index++)
{
if (!types1[index].equals(types2[index]))
{
return false;
}
}
return true;
}
/**
* Returns whether the given instruction opcode represents a dup or swap
* instruction (dup, dup_x1, dup_x2, dup2, dup2_x1, dup2_x2, swap).
*/
private boolean isDupOrSwap(int opcode)
{
return opcode >= InstructionConstants.OP_DUP &&
opcode <= InstructionConstants.OP_SWAP;
}
}