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

proguard.analysis.cpa.jvm.transfer.JvmTransferRelation 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.7
Show newest version
/*
 * ProGuardCORE -- library to process Java bytecode.
 *
 * Copyright (c) 2002-2022 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.analysis.cpa.jvm.transfer;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import proguard.analysis.datastructure.callgraph.Call;
import proguard.classfile.Clazz;
import proguard.classfile.Method;
import proguard.classfile.MethodSignature;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.instruction.BranchInstruction;
import proguard.classfile.instruction.ConstantInstruction;
import proguard.classfile.instruction.Instruction;
import proguard.classfile.instruction.SimpleInstruction;
import proguard.classfile.instruction.SwitchInstruction;
import proguard.classfile.instruction.VariableInstruction;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.util.ClassUtil;
import proguard.analysis.cpa.defaults.LatticeAbstractState;
import proguard.analysis.cpa.interfaces.AbstractState;
import proguard.analysis.cpa.interfaces.Precision;
import proguard.analysis.cpa.interfaces.ProgramLocationDependentTransferRelation;
import proguard.analysis.cpa.jvm.cfa.edges.JvmCallCfaEdge;
import proguard.analysis.cpa.jvm.cfa.edges.JvmCfaEdge;
import proguard.analysis.cpa.jvm.cfa.edges.JvmInstructionCfaEdge;
import proguard.analysis.cpa.jvm.cfa.nodes.JvmCfaNode;
import proguard.analysis.cpa.jvm.state.JvmAbstractState;
import proguard.analysis.cpa.jvm.util.ConstantLookupVisitor;
import proguard.analysis.cpa.jvm.util.InstructionClassifier;
import proguard.evaluation.ClassConstantValueFactory;
import proguard.evaluation.value.DoubleValue;
import proguard.evaluation.value.FloatValue;
import proguard.evaluation.value.IntegerValue;
import proguard.evaluation.value.LongValue;
import proguard.evaluation.value.ParticularValueFactory;
import proguard.evaluation.value.ReferenceValue;
import proguard.evaluation.value.Value;

/**
 * The {@link JvmTransferRelation} computes the successors of an {@link JvmAbstractState} for a given instruction. It stores category 2 computational types as tuples of the abstract state containing
 * the information about the value in the most significant bits and a default abstract state in the least significant bits of the big-endian notation.
 *
 * @author Dmitry Ivanov
 */
public abstract class JvmTransferRelation>
    implements ProgramLocationDependentTransferRelation
{

    // implementations for ProgramLocationDependentTransferRelation

    @Override
    public AbstractState getEdgeAbstractSuccessor(AbstractState abstractState, JvmCfaEdge edge, Precision precision)
    {
        if (!(abstractState instanceof JvmAbstractState))
        {
            throw new IllegalArgumentException(getClass().getName() + " does not support " + abstractState.getClass().getName());
        }
        JvmAbstractState state     = (JvmAbstractState) abstractState;
        JvmAbstractState successor = state.copy();

        if (edge instanceof JvmCallCfaEdge)
        {
            successor.setProgramLocation(edge.getSource().getLeavingEdges().stream().filter(JvmInstructionCfaEdge.class::isInstance).findFirst().get().getTarget());
            processCall(successor, ((JvmCallCfaEdge) edge).getCall());
        }
        else
        {
            successor.setProgramLocation(edge.getTarget());
            if (edge instanceof JvmInstructionCfaEdge)
            {
                Instruction instruction = ((JvmInstructionCfaEdge) edge).getInstruction();
                if (InstructionClassifier.isInvoke(instruction.opcode))
                {
                    return null;
                }
                successor = getAbstractSuccessorForInstruction(successor, ((JvmInstructionCfaEdge) edge).getInstruction(), state.getProgramLocation().getClazz(), precision);
            }
        }

        return successor;
    }

    /**
     * Returns the result of applying {@code instruction} to the {@code abstractState}.
     */
    protected JvmAbstractState getAbstractSuccessorForInstruction(JvmAbstractState abstractState, Instruction instruction, Clazz clazz, Precision precision)
    {
        instruction.accept(clazz, null, null, 0, new InstructionAbstractInterpreter(abstractState));
        return abstractState;
    }

    /**
     * Returns the result of the instruction application. The default implementation computes join over its arguments.
     */
    protected List applyInstruction(Instruction instruction, List operands, int resultCount)
    {
        List answer        = new ArrayList<>(resultCount);
        StateT       answerContent = operands.stream().reduce(getAbstractDefault(), StateT::join);
        for (int i = 0; i < resultCount; i++)
        {
            answer.add(answerContent);
        }
        return answer;
    }

    /**
     * Returns an abstract representation of a byte constant {@code b}.
     */
    public StateT getAbstractByteConstant(byte b)
    {
        return getAbstractDefault();
    }

    /**
     * Returns a default abstract state. In case of lattice abstract domains, it should be the bottom element.
     */
    public abstract StateT getAbstractDefault();

    /**
     * Returns an abstract representation of a double constant {@code d}.
     */
    public List getAbstractDoubleConstant(double d)
    {
        return Arrays.asList(getAbstractDefault(), getAbstractDefault());
    }

    /**
     * Returns an abstract representation of a float constant {@code f}.
     */
    public StateT getAbstractFloatConstant(float f)
    {
        return getAbstractDefault();
    }

    /**
     * Returns an abstract representation of an integer constant {@code i}.
     */
    public StateT getAbstractIntegerConstant(int i)
    {
        return getAbstractDefault();
    }

    /**
     * Returns an abstract representation of a long constant {@code l}.
     */
    public List getAbstractLongConstant(long l)
    {
        return Arrays.asList(getAbstractDefault(), getAbstractDefault());
    }

    /**
     * Returns an abstract representation of a null reference.
     */
    public StateT getAbstractNull()
    {
        return getAbstractDefault();
    }

    /**
     * Returns an abstract representation of a short constant {@code s}.
     */
    public StateT getAbstractShortConstant(short s)
    {
        return getAbstractDefault();
    }

    /**
     * Pops the arguments from the operand stack and passes them to {@code invokeMethod}.
     */
    protected void processCall(JvmAbstractState state, Call call)
    {
        boolean      isStatic = call.invocationOpcode == Instruction.OP_INVOKESTATIC || call.invocationOpcode == Instruction.OP_INVOKEDYNAMIC;
        List operands = new ArrayList<>();
        if (call.getTarget().descriptor.argumentTypes != null)
        {
            List argumentTypes = call.getTarget().descriptor.argumentTypes;
            for (int i = argumentTypes.size() - 1; i >= 0; i--)
            {
                boolean isCategory2 = ClassUtil.isInternalCategory2Type(argumentTypes.get(i));
                if (isCategory2)
                {
                    StateT higherByte = state.pop();
                    operands.add(state.pop());
                    operands.add(higherByte);
                }
                else
                {
                    operands.add(state.pop());
                }
            }
        }
        if (!isStatic)
        {
            operands.add(state.pop());
        }
        Collections.reverse(operands);
        invokeMethod(state, call, operands);
    }

    /**
     * The default implementation computes join over its arguments.
     */
    public void invokeMethod(JvmAbstractState state, Call call, List operands)
    {
        int    pushCount     = ClassUtil.internalTypeSize(call.getTarget().descriptor.returnType == null ? "?" : call.getTarget().descriptor.returnType);
        StateT answerContent = operands.stream().reduce(getAbstractDefault(), StateT::join);
        for (int i = 0; i < pushCount; i++)
        {
            state.push(answerContent);
        }
    }

    /**
     * Returns an abstract state representing the result of the {@code instanceof} operation.
     */
    protected StateT isInstanceOf(StateT state, String type)
    {
        return getAbstractDefault();
    }

    /**
     * This {@link InstructionVisitor} performs generic operations (e.g., loads, stores) parametrized by the specific behavior of {@link JvmTransferRelation} for instruction applications, method
     * invocations, and constructing literals.
     */
    private class InstructionAbstractInterpreter
        implements InstructionVisitor
    {

        private final JvmAbstractState  abstractState;
        private final ClassConstantValueFactory classConstantValueFactory = new ClassConstantValueFactory(new ParticularValueFactory());
        private final ConstantLookupVisitor     constantLookupVisitor     = new ConstantLookupVisitor();

        public InstructionAbstractInterpreter(JvmAbstractState abstractState)
        {
            this.abstractState = abstractState;
        }

        // implementations for InstructionVisitor

        @Override
        public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
        {
            switch (simpleInstruction.opcode)
            {
                case Instruction.OP_ACONST_NULL:
                    abstractState.push(getAbstractNull());
                    break;
                case Instruction.OP_ICONST_M1:
                case Instruction.OP_ICONST_0:
                case Instruction.OP_ICONST_1:
                case Instruction.OP_ICONST_2:
                case Instruction.OP_ICONST_3:
                case Instruction.OP_ICONST_4:
                case Instruction.OP_ICONST_5:
                    abstractState.push(getAbstractIntegerConstant(simpleInstruction.constant));
                    break;
                case Instruction.OP_LCONST_0:
                case Instruction.OP_LCONST_1:
                    abstractState.pushAll(getAbstractLongConstant(simpleInstruction.constant));
                    break;
                case Instruction.OP_FCONST_0:
                case Instruction.OP_FCONST_1:
                case Instruction.OP_FCONST_2:
                    abstractState.push(getAbstractFloatConstant(simpleInstruction.constant));
                    break;
                case Instruction.OP_DCONST_0:
                case Instruction.OP_DCONST_1:
                    abstractState.pushAll(getAbstractDoubleConstant(simpleInstruction.constant));
                    break;
                case Instruction.OP_IALOAD:
                case Instruction.OP_FALOAD:
                case Instruction.OP_AALOAD:
                case Instruction.OP_BALOAD:
                case Instruction.OP_CALOAD:
                case Instruction.OP_SALOAD:
                {
                    StateT index = abstractState.pop();
                    abstractState.push(abstractState.getArrayElementOrDefault(abstractState.pop(), index, getAbstractDefault()));
                    break;
                }
                case Instruction.OP_LALOAD:
                case Instruction.OP_DALOAD:
                {
                    StateT index = abstractState.pop();
                    StateT array = abstractState.pop();
                    abstractState.push(getAbstractDefault());
                    abstractState.push(abstractState.getArrayElementOrDefault(array, index, getAbstractDefault()));
                    break;
                }
                case Instruction.OP_IASTORE:
                case Instruction.OP_FASTORE:
                case Instruction.OP_AASTORE:
                case Instruction.OP_BASTORE:
                case Instruction.OP_CASTORE:
                case Instruction.OP_SASTORE:
                {
                    StateT value = abstractState.pop();
                    StateT index = abstractState.pop();
                    abstractState.setArrayElement(abstractState.pop(), index, value);
                    break;
                }
                case Instruction.OP_LASTORE:
                case Instruction.OP_DASTORE:
                {
                    StateT value = abstractState.pop();
                    abstractState.pop();
                    StateT index = abstractState.pop();
                    abstractState.setArrayElement(abstractState.pop(), index, value);
                    break;
                }
                case Instruction.OP_BIPUSH:
                    abstractState.push(getAbstractByteConstant((byte) simpleInstruction.constant));
                    break;
                case Instruction.OP_SIPUSH:
                    abstractState.push(getAbstractShortConstant((short) simpleInstruction.constant));
                    break;
                case Instruction.OP_POP:
                case Instruction.OP_MONITORENTER: // TODO synchronization is not yet modeled
                case Instruction.OP_MONITOREXIT:
                    abstractState.pop();
                    break;
                case Instruction.OP_POP2:
                    abstractState.pop();
                    abstractState.pop();
                    break;
                case Instruction.OP_DUP:
                    abstractState.push(abstractState.peek());
                    break;
                case Instruction.OP_DUP_X1:
                {
                    StateT state1 = abstractState.pop();
                    StateT state2 = abstractState.pop();
                    abstractState.push(state1);
                    abstractState.push(state2);
                    abstractState.push(state1);
                    break;
                }
                case Instruction.OP_DUP_X2:
                {
                    StateT state1 = abstractState.pop();
                    StateT state2 = abstractState.pop();
                    StateT state3 = abstractState.pop();
                    abstractState.push(state1);
                    abstractState.push(state3);
                    abstractState.push(state2);
                    abstractState.push(state1);
                    break;
                }
                case Instruction.OP_DUP2:
                {
                    StateT state1 = abstractState.peek();
                    StateT state2 = abstractState.peek(1);
                    abstractState.push(state2);
                    abstractState.push(state1);
                    break;
                }
                case Instruction.OP_DUP2_X1:
                {
                    StateT state1 = abstractState.pop();
                    StateT state2 = abstractState.pop();
                    StateT state3 = abstractState.pop();
                    abstractState.push(state2);
                    abstractState.push(state1);
                    abstractState.push(state3);
                    abstractState.push(state2);
                    abstractState.push(state1);
                    break;
                }
                case Instruction.OP_DUP2_X2:
                {
                    StateT state1 = abstractState.pop();
                    StateT state2 = abstractState.pop();
                    StateT state3 = abstractState.pop();
                    StateT state4 = abstractState.pop();
                    abstractState.push(state2);
                    abstractState.push(state1);
                    abstractState.push(state4);
                    abstractState.push(state3);
                    abstractState.push(state2);
                    abstractState.push(state1);
                    break;
                }
                case Instruction.OP_SWAP:
                {
                    StateT state1 = abstractState.pop();
                    StateT state2 = abstractState.pop();
                    abstractState.push(state1);
                    abstractState.push(state2);
                    break;
                }
                // in case of return we don't touch the value on the stack to be able to provide it to the caller
                case Instruction.OP_RETURN:
                case Instruction.OP_ARETURN:
                case Instruction.OP_DRETURN:
                case Instruction.OP_FRETURN:
                case Instruction.OP_LRETURN:
                case Instruction.OP_IRETURN:
                    break;
                case Instruction.OP_ATHROW:
                    StateT exceptionState = abstractState.pop();
                    abstractState.clearOperandStack();
                    abstractState.push(exceptionState);
                    break;
                default: // arithmetic instructions
                {
                    List operands = new ArrayList<>(simpleInstruction.stackPopCount(clazz));
                    // long shift instruction have to be considered separately because they have a category2 and a category1 parameter
                    if (InstructionClassifier.isLongShift(simpleInstruction.opcode))
                    {
                        operands.add(abstractState.pop());
                        StateT higherByte = abstractState.pop();
                        operands.add(abstractState.pop());
                        operands.add(higherByte);
                    }
                    else
                    {
                        for (int i = 0; i < simpleInstruction.stackPopCount(clazz) / (simpleInstruction.isCategory2() ? 2 : 1); i++)
                        {
                            if (simpleInstruction.isCategory2())
                            {
                                StateT higherByte = abstractState.pop();
                                operands.add(abstractState.pop());
                                operands.add(higherByte);
                            }
                            else
                            {
                                operands.add(abstractState.pop());
                            }
                        }
                    }
                    Collections.reverse(operands);
                    abstractState.pushAll(applyInstruction(simpleInstruction, operands, simpleInstruction.stackPushCount(clazz)));
                }
            }
        }

        @Override
        public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
        {
            if (variableInstruction.opcode == Instruction.OP_IINC)
            {
                List operands = new ArrayList<>(2);
                operands.add(abstractState.getVariableOrDefault(variableInstruction.variableIndex, getAbstractDefault()));
                operands.add(getAbstractIntegerConstant(variableInstruction.constant));
                List res = applyInstruction(variableInstruction, operands, 1);
                if (res.size() != 1)
                {
                    throw new IllegalStateException("applyInstruction should return a list of size 1");
                }
                abstractState.setVariable(variableInstruction.variableIndex, res.get(0), getAbstractDefault());
            }
            else if (variableInstruction.isLoad())
            {
                if (variableInstruction.isCategory2())
                {
                    abstractState.push(abstractState.getVariableOrDefault(variableInstruction.variableIndex + 1, getAbstractDefault()));
                }
                abstractState.push(abstractState.getVariableOrDefault(variableInstruction.variableIndex, getAbstractDefault()));
            }
            else if (variableInstruction.isStore())
            {
                abstractState.setVariable(variableInstruction.variableIndex, abstractState.pop(), getAbstractDefault());
                if (variableInstruction.isCategory2())
                {
                    abstractState.setVariable(variableInstruction.variableIndex + 1, abstractState.pop(), getAbstractDefault());
                }
            }
        }

        @Override
        public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
        {
            constantLookupVisitor.resetResult();
            switch (constantInstruction.opcode)
            {
                case Instruction.OP_LDC:
                case Instruction.OP_LDC_W:
                case Instruction.OP_LDC2_W:
                    abstractState.pushAll(getAbstactValue(classConstantValueFactory.constantValue(clazz, constantInstruction.constantIndex)));
                    break;
                case Instruction.OP_GETSTATIC:
                    constantLookupVisitor.isStatic = true;
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, constantLookupVisitor);
                    if (constantLookupVisitor.resultSize > 1)
                    {
                        abstractState.push(getAbstractDefault());
                    }
                    abstractState.push(abstractState.getStaticOrDefault(constantLookupVisitor.result, getAbstractDefault()));
                    break;
                case Instruction.OP_PUTSTATIC:
                {
                    constantLookupVisitor.isStatic = true;
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, constantLookupVisitor);
                    StateT value = abstractState.pop();
                    if (constantLookupVisitor.resultSize > 1)
                    {
                        abstractState.pop();
                    }
                    abstractState.setStatic(constantLookupVisitor.result, value);
                    break;
                }
                case Instruction.OP_GETFIELD:
                {
                    constantLookupVisitor.isStatic = false;
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, constantLookupVisitor);
                    StateT result = abstractState.getFieldOrDefault(abstractState.pop(), constantLookupVisitor.result, getAbstractDefault());
                    if (constantLookupVisitor.resultSize > 1)
                    {
                        abstractState.push(getAbstractDefault());
                    }
                    abstractState.push(result);
                    break;
                }
                case Instruction.OP_PUTFIELD:
                {
                    constantLookupVisitor.isStatic = false;
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, constantLookupVisitor);
                    StateT value = abstractState.pop();
                    if (constantLookupVisitor.resultSize > 1)
                    {
                        abstractState.pop();
                    }
                    abstractState.setField(abstractState.pop(), constantLookupVisitor.result, value);
                    break;
                }
                case Instruction.OP_INVOKESTATIC:
                case Instruction.OP_INVOKEDYNAMIC:
                case Instruction.OP_INVOKEVIRTUAL:
                case Instruction.OP_INVOKESPECIAL:
                case Instruction.OP_INVOKEINTERFACE:
                    throw new IllegalStateException(getClass().getName() + " encountered an unexpected call instruction");
                case Instruction.OP_INSTANCEOF:
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, constantLookupVisitor);
                    abstractState.push(isInstanceOf(abstractState.pop(), constantLookupVisitor.result));
                    break;
                case Instruction.OP_NEW:
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, constantLookupVisitor);
                    abstractState.push(abstractState.newObject(constantLookupVisitor.result));
                    break;
                case Instruction.OP_NEWARRAY:
                    abstractState.push(abstractState.newArray(String.valueOf(proguard.classfile.instruction.InstructionUtil.internalTypeFromArrayType((byte) constantInstruction.constant)),
                                                              Collections.singletonList(abstractState.pop())));
                    break;
                case Instruction.OP_ANEWARRAY:
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, constantLookupVisitor);
                    abstractState.push(abstractState.newArray(constantLookupVisitor.result,
                                                              Collections.singletonList(abstractState.pop())));
                    break;
                case Instruction.OP_MULTIANEWARRAY:
                {
                    List dimensions = new ArrayList<>(constantInstruction.constant);
                    for (int i = 0; i < constantInstruction.stackPopCount(clazz); i++)
                    {
                        dimensions.add(abstractState.pop());
                    }
                    Collections.reverse(dimensions);
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, constantLookupVisitor);
                    abstractState.push(abstractState.newArray(constantLookupVisitor.result,
                                                              dimensions));
                    break;
                }
                case Instruction.OP_CHECKCAST:
                    break;
                default: // should never happen
                    throw new InvalidParameterException("The opcode " + constantInstruction.opcode + " is not supported by the constant instruction visitor");
            }
        }

        @Override
        public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
        {
            for (int i = 0; i < branchInstruction.stackPopCount(clazz); i++)
            {
                abstractState.pop();
            }
            for (int i = 0; i < branchInstruction.stackPushCount(clazz); i++)
            {
                abstractState.push(getAbstractDefault());
            }
        }

        @Override
        public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
        {
            for (int i = 0; i < switchInstruction.stackPopCount(clazz); i++)
            {
                abstractState.pop();
            }
        }

        private List getAbstactValue(Value value)
        {
            if (value instanceof IntegerValue)
            {
                return Collections.singletonList(getAbstractIntegerConstant(((IntegerValue) value).value()));
            }
            else if (value instanceof LongValue)
            {
                return getAbstractLongConstant(((LongValue) value).value());
            }
            else if (value instanceof FloatValue)
            {
                return Collections.singletonList(getAbstractFloatConstant(((FloatValue) value).value()));
            }
            else if (value instanceof DoubleValue)
            {
                return getAbstractDoubleConstant(((DoubleValue) value).value());
            }
            else if (value instanceof ReferenceValue)
            {
                return Collections.singletonList(getAbstractDefault());
            }
            return Collections.singletonList(getAbstractDefault());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy