All Downloads are FREE. Search and download functionalities are using the official Maven repository.

proguard.classfile.util.ArrayInitializationMatcher Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.6
Show newest version
/*
 * 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