proguard.classfile.util.ArrayInitializationMatcher 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.classfile.util;
import proguard.classfile.*;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.constant.Constant;
import proguard.classfile.editor.InstructionSequenceReplacer;
import proguard.classfile.instruction.*;
import proguard.evaluation.*;
import proguard.evaluation.value.*;
/**
* This class finds sequences of instructions that correspond to primitive
* array initializations. Such initializations may be represented more
* efficiently in other bytecode languages.
*
* @author Eric Lafortune
*/
public class ArrayInitializationMatcher
{
private static final int X = InstructionSequenceReplacer.X;
private final PartialEvaluator partialEvaluator;
private int arrayInitializationStart;
private int arrayInitializationEnd;
private Object array;
private final Constant[] CONSTANTS = new Constant[0];
// Conversion with dex2jar might result in arrays
// being pre-stored to a variable before initialization:
//
// newarray
// astore X
// aload X
// initialization start
//
private final Instruction[] ARRAY_PRESTORE_INSTRUCTIONS = new Instruction[]
{
new VariableInstruction(Instruction.OP_ASTORE, X),
new VariableInstruction(Instruction.OP_ALOAD, X)
};
private final InstructionSequenceMatcher arrayPreStoreMatcher =
new InstructionSequenceMatcher(CONSTANTS, ARRAY_PRESTORE_INSTRUCTIONS);
/**
* Creates a new ArrayInitializationMatcher.
*/
public ArrayInitializationMatcher()
{
this(new PartialEvaluator());
}
/**
* Creates a new ArrayInitializationMatcher that will use the given partial
* evaluator. The evaluator must have been initialized before trying to
* match any array initializations.
* @param partialEvaluator the evaluator to be used for the analysis.
*/
public ArrayInitializationMatcher(PartialEvaluator partialEvaluator)
{
this.partialEvaluator = partialEvaluator;
}
/**
* Returns whether the code fragment starting at the specified newarray
* instruction is followed by a static array initialization.
* @param clazz the class.
* @param method the method.
* @param codeAttribute the code attribute.
* @param newArrayOffset the offset of the newarray instruction.
* @param newArrayInstruction the newarray instruction.
* @return whether there is a static array initialization.
*/
public boolean matchesArrayInitialization(Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int newArrayOffset,
SimpleInstruction newArrayInstruction)
{
array = null;
TracedStack stackBefore =
partialEvaluator.getStackBefore(newArrayOffset);
IntegerValue integerValue =
stackBefore.getTop(0).integerValue();
if (!integerValue.isParticular())
{
return false;
}
int arrayLength = integerValue.value();
int newArrayType = newArrayInstruction.constant;
int arrayStoreOpcode = arrayStoreOpcode(newArrayType);
byte[] code = codeAttribute.code;
int offset = newArrayOffset;
Instruction instruction = newArrayInstruction;
int skipOffset = skipPreStoreInstructions(clazz, method, codeAttribute, offset + instruction.length(offset));
if (skipOffset > 0)
{
offset = skipOffset;
newArrayOffset = offset;
instruction = InstructionFactory.create(code, offset);
}
// Remember the potential initialization start.
int tmpInitializationStart = offset + instruction.length(offset);
// Check if all the elements in the array are initialized.
for (int index = 0; index < arrayLength; index++)
{
// Check if the array reference is pushed.
instruction = InstructionFactory.create(code, offset += instruction.length(offset));
if (instruction.stackPushCount(clazz) < 1 ||
!partialEvaluator.getStackAfter(offset).getTopActualProducerValue(0).instructionOffsetValue().contains(newArrayOffset))
{
return false;
}
// Check that the array index is pushed.
instruction = InstructionFactory.create(code, offset += instruction.length(offset));
if (instruction.stackPushCount(clazz) != 1)
{
return false;
}
Value indexValue = partialEvaluator.getStackAfter(offset).getTop(0);
if (indexValue.computationalType() != Value.TYPE_INTEGER ||
!indexValue.integerValue().isParticular() ||
indexValue.integerValue().value() != index)
{
return false;
}
// Check if a particular value is pushed.
instruction = InstructionFactory.create(code, offset += instruction.length(offset));
if (instruction.stackPushCount(clazz) < 1 ||
!partialEvaluator.getStackAfter(offset).getTop(0).isParticular())
{
return false;
}
Value elementValue = partialEvaluator.getStackAfter(offset).getTop(0);
// Check if the value is stored in the array.
instruction = InstructionFactory.create(code, offset += instruction.length(offset));
if (instruction.opcode != arrayStoreOpcode)
{
return false;
}
if (index == 0)
{
array = newArray(newArrayType, arrayLength);
}
arrayStore(newArrayType, array, index, elementValue);
}
arrayInitializationStart = tmpInitializationStart;
arrayInitializationEnd = offset;
return offset > newArrayOffset;
}
/**
* Returns the offset to skip to after a new-array instruction.
*
* This is a work-around for code converted by dex2jar. In some
* cases, after an array has been created, it is immediately
* stored into a variable and loaded again:
*
* newarray
* astore X
* aload X
* initialization
*
*
* @param clazz the class.
* @param method the method.
* @param codeAttribute the code attribute.
* @param startOffset the start offset.
* @return the offset to skip to
*/
private int skipPreStoreInstructions(Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int startOffset)
{
final int instructionCount = arrayPreStoreMatcher.instructionCount();
arrayPreStoreMatcher.reset();
for (int count = 0, offset = startOffset;
count < instructionCount && offset < codeAttribute.u4codeLength;
count++)
{
Instruction instruction =
InstructionFactory.create(codeAttribute.code, offset);
instruction.accept(clazz, method, codeAttribute, offset,
arrayPreStoreMatcher);
offset += instruction.length(offset);
}
if (arrayPreStoreMatcher.isMatching())
{
return arrayPreStoreMatcher.matchedInstructionOffset(instructionCount - 1);
}
return -1;
}
/**
* Returns the first offset of the recent static array initialization, i.e. the first
* initialization instruction after the newarray.
* @see #matchesArrayInitialization
*/
public int arrayInitializationStart()
{
return arrayInitializationStart;
}
/**
* Returns the last offset of the recent static array initialization.
* @see #matchesArrayInitialization
*/
public int arrayInitializationEnd()
{
return arrayInitializationEnd;
}
/**
* Returns the recent static array initialization.
* @see #matchesArrayInitialization
*/
public Object array()
{
return array;
}
// Small utility methods.
/**
* Returns the Value type that corresponds to the given array type.
*/
private byte internalType(int newArrayType)
{
switch (newArrayType)
{
case Instruction.ARRAY_T_BOOLEAN:
case Instruction.ARRAY_T_BYTE:
case Instruction.ARRAY_T_CHAR:
case Instruction.ARRAY_T_SHORT:
case Instruction.ARRAY_T_INT: return Value.TYPE_INTEGER;
case Instruction.ARRAY_T_LONG: return Value.TYPE_LONG;
case Instruction.ARRAY_T_FLOAT: return Value.TYPE_FLOAT;
case Instruction.ARRAY_T_DOUBLE: return Value.TYPE_DOUBLE;
default:
throw new IllegalArgumentException("Unexpected new array type ["+newArrayType+"]");
}
}
/**
* Returns the _astore instruction opcode that corresponds to the given
* array type.
*/
private byte arrayStoreOpcode(int newArrayType)
{
switch (newArrayType)
{
case Instruction.ARRAY_T_BOOLEAN:
case Instruction.ARRAY_T_BYTE: return Instruction.OP_BASTORE;
case Instruction.ARRAY_T_CHAR: return Instruction.OP_CASTORE;
case Instruction.ARRAY_T_SHORT: return Instruction.OP_SASTORE;
case Instruction.ARRAY_T_INT: return Instruction.OP_IASTORE;
case Instruction.ARRAY_T_LONG: return Instruction.OP_LASTORE;
case Instruction.ARRAY_T_FLOAT: return Instruction.OP_FASTORE;
case Instruction.ARRAY_T_DOUBLE: return Instruction.OP_DASTORE;
default:
throw new IllegalArgumentException("Unexpected new array type ["+newArrayType+"]");
}
}
/**
* Returns am array of the given array type and with the given length.
*/
private Object newArray(int newArrayType, int arrayLength)
{
switch (newArrayType)
{
case Instruction.ARRAY_T_BOOLEAN: return new boolean[arrayLength];
case Instruction.ARRAY_T_BYTE: return new byte[arrayLength];
case Instruction.ARRAY_T_CHAR: return new char[arrayLength];
case Instruction.ARRAY_T_SHORT: return new short[arrayLength];
case Instruction.ARRAY_T_INT: return new int[arrayLength];
case Instruction.ARRAY_T_LONG: return new long[arrayLength];
case Instruction.ARRAY_T_FLOAT: return new float[arrayLength];
case Instruction.ARRAY_T_DOUBLE: return new double[arrayLength];
default:
throw new IllegalArgumentException("Unexpected new array type ["+newArrayType+"]");
}
}
/**
* Fills out the given value at the given index in the given array of the
* given array type.
*/
private void arrayStore(int newArrayType, Object array, int index, Value value)
{
switch (newArrayType)
{
case Instruction.ARRAY_T_BOOLEAN: ((boolean[])array)[index] = 0 != value.integerValue().value(); break;
case Instruction.ARRAY_T_BYTE: ((byte [])array)[index] = (byte) value.integerValue().value(); break;
case Instruction.ARRAY_T_CHAR: ((char [])array)[index] = (char) value.integerValue().value(); break;
case Instruction.ARRAY_T_SHORT: ((short [])array)[index] = (short)value.integerValue().value(); break;
case Instruction.ARRAY_T_INT: ((int [])array)[index] = value.integerValue().value(); break;
case Instruction.ARRAY_T_LONG: ((long [])array)[index] = value.longValue().value(); break;
case Instruction.ARRAY_T_FLOAT: ((float [])array)[index] = value.floatValue().value(); break;
case Instruction.ARRAY_T_DOUBLE: ((double [])array)[index] = value.doubleValue().value(); break;
default:
throw new IllegalArgumentException("Unexpected new array type ["+newArrayType+"]");
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy