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

proguard.optimize.ExpectedStackTypeFinder Maven / Gradle / Ivy

Go to download

ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode

There is a newer version: 7.6.0
Show newest version
/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2021 Guardsquare NV
 *
 * 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;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.ClassEstimates;
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.util.ArrayUtil;

/**
 * This AttributeVisitor finds all the reference types that are expected on the
 * stack, based on their consuming instructions.
 *
 * @author Eric Lafortune
 */
public class ExpectedStackTypeFinder
implements   AttributeVisitor,
             InstructionVisitor,
             ConstantVisitor,
             MemberVisitor
{
    private static final Logger logger = LogManager.getLogger(ExpectedStackTypeFinder.class);


    protected final PartialEvaluator partialEvaluator;
    private   final boolean          runPartialEvaluator;

    private ReferenceValue[] expectedTypes = new ReferenceValue[ClassEstimates.TYPICAL_CODE_LENGTH];

    // Parameters and return values for visitor methods.
    private int     referencingOffset;
    private boolean isPut;
    private boolean isStatic;
    private Clazz   referencedClass;
    private Clazz[] referencedClasses;

    private boolean reRunFromStart;


    /**
     * Creates a new ExpectedStackTypeFinder.
     */
    public ExpectedStackTypeFinder()
    {
        this(new TypedReferenceValueFactory());
    }


    /**
     * Creates a new ExpectedStackTypeFinder.
     */
    public ExpectedStackTypeFinder(ValueFactory valueFactory)
    {
        this(valueFactory,
             new ReferenceTracingValueFactory(valueFactory, false));
    }


    /**
     * Creates a new ExpectedStackTypeFinder.
     */
    public ExpectedStackTypeFinder(ValueFactory                 valueFactory,
                                   ReferenceTracingValueFactory tracingValueFactory)
    {
        this(new PartialEvaluator(tracingValueFactory,
                                  new ReferenceTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)),
                                  true,
                                  tracingValueFactory),
             true);
    }


    /**
     * Creates a new ParameterModificationMarker.
     */
    public ExpectedStackTypeFinder(PartialEvaluator partialEvaluator,
                                   boolean          runPartialEvaluator)
    {
        this.partialEvaluator    = partialEvaluator;
        this.runPartialEvaluator = runPartialEvaluator;
    }


    /**
     * Returns the reference type that is expected on the stack from the
     * specified producing instruction, based on all consuming instructions
     * in the code attribute that was visited most recently.
     */
    public ReferenceValue getExpectedTypeAfter(int instructionOffset)
    {
        return expectedTypes[instructionOffset];
    }


    /**
     * Returns the 'new' instruction offset at which the object instance is
     * created that is initialized at the given offset.
     */
    public int creationOffset(int initializationOffset)
    {
        int stackEntryIndex =
            partialEvaluator.getStackAfter(initializationOffset).size();

        TracedReferenceValue tracedReferenceValue =
            (TracedReferenceValue)partialEvaluator.getStackBefore(initializationOffset).getBottom(stackEntryIndex);

        InstructionOffsetValue producerOffsets =
            tracedReferenceValue.getTraceValue().instructionOffsetValue();

        return producerOffsets.instructionOffset(0);
    }


    // Implementations for AttributeVisitor.

    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}


    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
    {
        logger.debug("ExpectedStackTypeFinder: [{}.{}{}]",
                     clazz.getName(),
                     method.getName(clazz),
                     method.getDescriptor(clazz)
        );

        // Initialized the results.
        expectedTypes =
            ArrayUtil.ensureArraySize(expectedTypes,
                                      codeAttribute.u4codeLength,
                                      null);

        // Evaluate the code.
        if (runPartialEvaluator)
        {
            partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
        }

        // Check all consuming instructions, to mark the producing instructions.
        // We're traversing the instructions backward, so the types required
        // by subsequent 'aaload' instructions for multidimensional arrays can
        // propagate. In case the instructions are not sequential (e.g. due to
        // code obfuscation), we need to re-run the evaluation to correctly
        // propagate the types.

        do
        {
            reRunFromStart = false;
            for (int offset = codeAttribute.u4codeLength - 1; offset >= 0; offset--)
            {
                if (partialEvaluator.isTraced(offset))
                {
                    codeAttribute.instructionAccept(clazz, method, offset, this);
                }
            }

            if (reRunFromStart)
            {
                logger.debug("ExpectedStackTypeFinder: repeat [{}.{}{}]",
                             clazz.getName(),
                             method.getName(clazz),
                             method.getDescriptor(clazz)
                );
            }
        }
        while (reRunFromStart);

        logger.debug("ExpectedStackTypeFinder: results");

        if (logger.getLevel().isLessSpecificThan(Level.DEBUG))
        {
            int offset = 0;
            do {
                Instruction instruction = InstructionFactory.create(codeAttribute.code,
                        offset);
                ReferenceValue expectedType = expectedTypes[offset];

                if (expectedType == null) {
                    logger.debug(instruction.toString(offset));
                } else {
                    logger.debug("{} -> [{}]", instruction.toString(offset), expectedType);
                }

                offset += instruction.length(offset);
            }
            while (offset < codeAttribute.u4codeLength);
        }
    }


    // Implementations for InstructionVisitor.

    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}


    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
    {
        switch (simpleInstruction.opcode)
        {
            case Instruction.OP_IALOAD:
                ensureType("[I",
                           null,
                           offset,
                           1);
                break;

            case Instruction.OP_LALOAD:
                ensureType("[J",
                           null,
                           offset,
                           1);
                break;

            case Instruction.OP_FALOAD:
                ensureType("[F",
                           null,
                           offset,
                           1);
                break;

            case Instruction.OP_DALOAD:
                ensureType("[D",
                           null,
                           offset,
                           1);
                break;

            case Instruction.OP_AALOAD:
                ReferenceValue expectedType = expectedTypes[offset];
                ensureType(expectedType == null  ||
                           expectedType.getType() == null ?
                               TypeConstants.ARRAY + ClassConstants.TYPE_JAVA_LANG_OBJECT :
                               TypeConstants.ARRAY + ClassUtil.internalTypeFromClassType(expectedType.getType()),
                           expectedType == null ?  null : expectedType.getReferencedClass(),
                           offset,
                           1);
                break;

            case Instruction.OP_BALOAD:
                ensureType("[B",
                           null,
                           offset,
                           1);
                break;

            case Instruction.OP_CALOAD:
                ensureType("[C",
                           null,
                           offset,
                           1);
                break;

            case Instruction.OP_SALOAD:
                ensureType("[S",
                           null,
                           offset,
                           1);
                break;

            case Instruction.OP_IASTORE:
                ensureType("[I",
                           null,
                           offset,
                           2);
                break;

            case Instruction.OP_LASTORE:
                ensureType("[J",
                           null,
                           offset,
                           3);
                break;

            case Instruction.OP_FASTORE:
                ensureType("[F",
                           null,
                           offset,
                           2);
                break;

            case Instruction.OP_DASTORE:
                ensureType("[D",
                           null,
                           offset,
                           3);
                break;

            case Instruction.OP_AASTORE:
                ensureType(TypeConstants.ARRAY + ClassConstants.TYPE_JAVA_LANG_OBJECT,
                           null,
                           offset,
                           2);
                break;

            case Instruction.OP_BASTORE:
                ensureType("[B",
                           null,
                           offset,
                           2);
                break;

            case Instruction.OP_CASTORE:
                ensureType("[C",
                           null,
                           offset,
                           2);
                break;

            case Instruction.OP_SASTORE:
                ensureType("[S",
                           null,
                           offset,
                           2);
                break;

            case Instruction.OP_ARETURN:
            {
                String descriptor = method.getDescriptor(clazz);
                Clazz[] referencedClasses = ((ProgramMethod)method).referencedClasses;
                ensureType(ClassUtil.internalClassTypeFromType(ClassUtil.internalMethodReturnType(descriptor)),
                           referencedClasses == null ||
                           !ClassUtil.isInternalClassType(descriptor) ? null :
                               referencedClasses[new DescriptorClassEnumeration(descriptor).classCount() - 1],
                           offset,
                           0);
                break;
            }

            case Instruction.OP_ARRAYLENGTH:
            {
                // We'll need an array type of objects or of primitives.
                String actualType = partialEvaluator.getStackBefore(offset).getTop(0).referenceValue().internalType();
                ensureType(ClassUtil.isInternalClassType(actualType) ?
                               TypeConstants.ARRAY + ClassConstants.TYPE_JAVA_LANG_OBJECT :
                               actualType,
                           null,
                           offset,
                           0);
                break;
            }

            case Instruction.OP_ATHROW:
                // TODO: Pass referenced class.
                ensureType(ClassConstants.NAME_JAVA_LANG_THROWABLE,
                           null,
                           offset,
                           0);
                break;
        }
    }


    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
    {
        switch (constantInstruction.opcode)
        {
            case Instruction.OP_GETSTATIC:
                referencingOffset = offset;
                isPut             = false;
                isStatic          = true;
                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                break;

            case Instruction.OP_PUTSTATIC:
                referencingOffset = offset;
                isPut             = true;
                isStatic          = true;
                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                break;

            case Instruction.OP_GETFIELD:
                referencingOffset = offset;
                isPut             = false;
                isStatic          = false;
                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                break;

            case Instruction.OP_PUTFIELD:
                referencingOffset = offset;
                isPut             = true;
                isStatic          = false;
                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                break;

            case Instruction.OP_INVOKESTATIC:
                referencingOffset = offset;
                isStatic          = true;
                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                break;

            case Instruction.OP_INVOKEVIRTUAL:
            case Instruction.OP_INVOKESPECIAL:
            case Instruction.OP_INVOKEINTERFACE:
            case Instruction.OP_INVOKEDYNAMIC:
                referencingOffset = offset;
                isStatic          = false;
                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                break;
        }
    }


    // Implementations for ConstantVisitor.

    public void visitAnyConstant(Clazz clazz, Constant constant) {}


    public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant)
    {
        // Check dynamically invoking a method.
        String methodDescriptor = invokeDynamicConstant.getType(clazz);

        int stackEntryIndex =
            ClassUtil.internalMethodParameterSize(methodDescriptor);

        // Check the method parameter types.
        Clazz[] referencedClasses = invokeDynamicConstant.referencedClasses;

        InternalTypeEnumeration types =
            new InternalTypeEnumeration(methodDescriptor);

        int index = 0;
        while (types.hasMoreTypes())
        {
            String type = types.nextType();

            stackEntryIndex -= ClassUtil.internalTypeSize(type);

            if (!ClassUtil.isInternalPrimitiveType(type.charAt(0)))
            {
                ensureType(ClassUtil.internalClassTypeFromType(type),
                           referencedClasses == null ? null : referencedClasses[index++],
                           referencingOffset,
                           stackEntryIndex);
            }
        }
    }


    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
    {
        // Check loading or storing a value in a field.
        String fieldType = fieldrefConstant.getType(clazz);

        // Check the field's class type.
        if (!isStatic)
        {
            referencedClass = null;
            clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this);

            ensureType(fieldrefConstant.getClassName(clazz),
                       referencedClass,
                       referencingOffset,
                       isPut ? ClassUtil.internalTypeSize(fieldType) : 0);
        }

        // Check the field type.
        if (isPut && !ClassUtil.isInternalPrimitiveType(fieldType.charAt(0)))
        {
            referencedClass = null;
            fieldrefConstant.referencedFieldAccept(this);

            ensureType(ClassUtil.internalClassTypeFromType(fieldType),
                       referencedClass,
                       referencingOffset,
                       0);
        }
    }


    public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant)
    {
        // Check invoking a method.
        String methodDescriptor = anyMethodrefConstant.getType(clazz);

        int stackEntryIndex =
            ClassUtil.internalMethodParameterSize(methodDescriptor);

        // Check the method's class type.
        if (!isStatic)
        {
            referencedClass = null;
            clazz.constantPoolEntryAccept(anyMethodrefConstant.u2classIndex, this);

            ensureType(anyMethodrefConstant.getClassName(clazz),
                       referencedClass,
                       referencingOffset,
                       stackEntryIndex);
        }

        // Check the method parameter types.
        referencedClasses = null;
        anyMethodrefConstant.referencedMethodAccept(this);

        InternalTypeEnumeration types =
            new InternalTypeEnumeration(methodDescriptor);

        int index = 0;
        while (types.hasMoreTypes())
        {
            String type = types.nextType();

            stackEntryIndex -= ClassUtil.internalTypeSize(type);

            if (!ClassUtil.isInternalPrimitiveType(type.charAt(0)))
            {
                Clazz referencedClass1 =
                    referencedClasses == null ||
                    !ClassUtil.isInternalClassType(type) ? null :
                        referencedClasses[index++];

                ensureType(ClassUtil.internalClassTypeFromType(type),
                           referencedClass1,
                           referencingOffset,
                           stackEntryIndex);
            }
        }
    }


    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
    {
        referencedClass = classConstant.referencedClass;
    }


    // Implementations for MemberVisitor.

    public void visitProgramField(ProgramClass programClass, ProgramField programField)
    {
        referencedClass = programField.referencedClass;
    }


    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
    {
        referencedClasses = programMethod.referencedClasses;
    }


    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
    {
        referencedClass = libraryField.referencedClass;
    }


    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
    {
        referencedClasses = libraryMethod.referencedClasses;
    }


    // Small utility methods.

    /**
     * Ensures that the specified stack entry at the given consumer
     * instruction offset has the given reference type.
     */
    private void ensureType(String type,
                            Clazz  referencedClass,
                            int    offset,
                            int    stackEntryIndex)
    {
        TracedStack stackBefore = partialEvaluator.getStackBefore(offset);

        TracedReferenceValue stackEntry =
            (TracedReferenceValue)stackBefore.getTop(stackEntryIndex);

        InstructionOffsetValue instructionOffsets =
            stackEntry.getTraceValue().instructionOffsetValue();

        int instructionOffsetCount =
            instructionOffsets.instructionOffsetCount();

        for (int index = 0; index < instructionOffsetCount; index++)
        {
            if (!instructionOffsets.isMethodParameter(index) &&
                !instructionOffsets.isExceptionHandler(index))
            {
                int producerOffset = instructionOffsets.instructionOffset(index);

                ReferenceValue oldExpectedType =
                    expectedTypes[producerOffset];

                String debugMessage = String.format("[%s]: [%s] old [%s] & new [%s]",
                                                    offset,
                                                    producerOffset,
                                                    oldExpectedType,
                                                    type
                );

                ReferenceValue newExpectedType =
                    new TypedReferenceValue(type, referencedClass, false, false);

                // Pick the most specific type, so all consumers are satisfied.
                if (oldExpectedType == null                  ||
                    oldExpectedType.isNull() == Value.ALWAYS ||
                    newExpectedType.instanceOf(oldExpectedType.getType(), oldExpectedType.getReferencedClass()) == Value.ALWAYS)
                {
                    // The new type is more specific than the old type,
                    // so pick the new type.
                    expectedTypes[producerOffset] = newExpectedType;

                    // Check if we need to revisit an earlier producer.
                    if (producerOffset > offset  &&
                        (oldExpectedType == null ||
                        !oldExpectedType.getType().equals(newExpectedType.getType())))
                    {
                        reRunFromStart = true;

                        debugMessage += String.format(" (rerun for higher producer [%s])", producerOffset);
                    }
                }
                else if (oldExpectedType.instanceOf(type, referencedClass) == Value.ALWAYS)
                {
                    // The old type is more specific than the new type,
                    // so leave the old type.
                }
                else
                {
                    // No type is more specific than the other type.
                    // There might be multiple solutions.
                    // Cheat by picking the type that is actually pushed.
                    ReferenceValue actualValue =
                        partialEvaluator.getStackAfter(producerOffset).getTop(0).referenceValue();

                    if (oldExpectedType != null &&
                        actualValue.getType() == null)
                    {
                        // The new type is null, so leave the old type.
                        continue;
                    }

                    newExpectedType =
                        new TypedReferenceValue(actualValue.getType(),
                                                actualValue.getReferencedClass(),
                                                false,
                                                false);

                    expectedTypes[producerOffset] = newExpectedType;

                    // Check if we need to revisit an earlier producer.
                    if (producerOffset > offset  &&
                        (oldExpectedType == null ||
                         !oldExpectedType.getType().equals(newExpectedType.getType())))
                    {
                        reRunFromStart = true;

                        debugMessage += String.format(" (rerun for higher producer [%s])", producerOffset);
                    }
                }

                debugMessage += String.format(" => [%s]", expectedTypes[producerOffset]);
                logger.debug(debugMessage);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy