proguard.preverify.CodePreverifier Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of proguard-core Show documentation
Show all versions of proguard-core Show documentation
ProGuardCORE is a free library to read, analyze, modify, and write Java class files.
/*
* ProGuardCORE -- library to process Java bytecode.
*
* Copyright (c) 2002-2020 Guardsquare NV
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package proguard.preverify;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.util.*;
import proguard.classfile.visitor.ClassPrinter;
import proguard.evaluation.*;
import proguard.evaluation.value.*;
import java.util.*;
/**
* This {@link AttributeVisitor} adds preverification information (for Java Micro
* Edition or for Java 6 or higher) to the code attributes that it visits.
*
* @author Eric Lafortune
*/
public class CodePreverifier
implements AttributeVisitor
{
//*
private static final boolean DEBUG = false;
/*/
private static boolean DEBUG = System.getProperty("cp") != null;
//*/
private final static Logger logger = LogManager.getLogger(CodePreverifier.class);
private static final int AT_METHOD_ENTRY = -1;
private final boolean microEdition;
private final ReferenceTracingValueFactory referenceTracingValueFactory = new ReferenceTracingValueFactory(new TypedReferenceValueFactory());
private final PartialEvaluator partialEvaluator = new PartialEvaluator(referenceTracingValueFactory,
new ReferenceTracingInvocationUnit(new BasicInvocationUnit(referenceTracingValueFactory)),
true,
referenceTracingValueFactory);
private final InitializationFinder initializationFinder = new InitializationFinder(partialEvaluator, false);
private final LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer(partialEvaluator, false, initializationFinder, false);
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
/**
* 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)
{
logger.error("Unexpected error while preverifying:");
logger.error(" Class = [{}]", clazz.getName());
logger.error(" Method = [{}]", method.getName(clazz)+method.getDescriptor(clazz));
logger.error(" 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;
int codeLength = codeAttribute.u4codeLength;
// Evaluate the method.
partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
initializationFinder.visitCodeAttribute(clazz, method, codeAttribute);
livenessAnalyzer.visitCodeAttribute(clazz, method, codeAttribute);
// We may have to remove unreachable code.
codeAttributeEditor.reset(codeLength);
// Collect the stack map frames.
List stackMapFrameList = new ArrayList();
for (int offset = 0; offset < codeLength; offset++)
{
// Only store frames at the beginning of code blocks.
if (!partialEvaluator.isTraced(offset))
{
// Mark the unreachable instruction for deletion.
codeAttributeEditor.deleteInstruction(offset);
}
else if (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,
AT_METHOD_ENTRY,
partialEvaluator.getVariablesBefore(0));
// Special case: the method.
if (method.getName(programClass).equals(ClassConstants.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 ?
Attribute.STACK_MAP :
Attribute.STACK_MAP_TABLE;
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());
}
}
// Apply code modifications, deleting unreachable code.
codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
}
// 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 typeCount = 0;
if (offset == AT_METHOD_ENTRY)
{
// Count the number of parameters, including any parameters
// that are unused and overwritten right away.
typeCount = ClassUtil.internalMethodParameterCount(
programMethod.getDescriptor(programClass),
programMethod.getAccessFlags());
}
else
{
typeCount = 0;
int maximumVariablesSize = variables.size();
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 (or uninitialized "this"
// type) index. A dead uninitialized "this" is not possible in
// plain Java code, but it is possible in optimized code and
// in other languages like Kotlin (in exception handlers).
// It has to be marked too ("flagThisUninit" in the JVM specs).
if (value != null &&
(livenessAnalyzer.isAliveBefore(offset, index) ||
isUninitalizedThis(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];
int 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;
// Is the value not null and alive (or uninitialized "this")?
if (value != null &&
(offset == AT_METHOD_ENTRY ||
livenessAnalyzer.isAliveBefore(offset, index) ||
isUninitalizedThis(offset, index)))
{
type = correspondingVerificationType(programClass,
programMethod,
codeAttribute,
offset,
value,
producerValue);
// Category 2 types that are alive are stored as single entries.
if (value.isCategory2())
{
index++;
}
}
else
{
// A null value at method entry means that there was a branch to
// offset 0 that has cleared the value. Then pick a dummy value so
// it never matches the next frame at offset 0.
type = offset == AT_METHOD_ENTRY ?
VerificationTypeFactory.createUninitializedThisType() :
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,
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,
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 != AT_METHOD_ENTRY)
{
TracedReferenceValue tracedReferenceValue =
(TracedReferenceValue)referenceValue;
InstructionOffsetValue instructionOffsetValue =
tracedReferenceValue.getTraceValue().instructionOffsetValue();
if (instructionOffsetValue.instructionOffsetCount() == 1)
{
// Is it a method parameter?
if (instructionOffsetValue.isMethodParameter(0))
{
// Is the parameter an uninitialized "this"?
if (isUninitalizedThis(offset,
instructionOffsetValue.methodParameter(0)))
{
// It's an UninitializedThis type.
return VerificationTypeFactory.createUninitializedThisType();
}
}
// Is it a newly created instance?
else if (instructionOffsetValue.isNewinstance(0))
{
int producerOffset = instructionOffsetValue.instructionOffset(0);
// Is it still uninitialized?
if (!initializationFinder.isInitializedBefore(offset, instructionOffsetValue))
{
// 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 wheter the specified variable is an uninitialized "this" at the
* given instruction offset.
*/
private boolean isUninitalizedThis(int offset, int variableIndex)
{
return
variableIndex == 0 &&
initializationFinder.isInitializer() &&
offset <= initializationFinder.superInitializationOffset();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy