/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2018 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.info;

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.instruction.*;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import proguard.evaluation.*;
import proguard.evaluation.value.*;
import proguard.optimize.evaluation.*;

/**
 * This MemberVisitor, AttributeVisitor, and InstructionVisitor marks the
 * reference parameters that are escaping, that are modified, or that are
 * returned.
 *
 * It also marks methods that may modify anything on the heap.
 *
 * The class must be called as a MemberVisitor on all members (to mark the
 * parameters of native methods, without code attributes), then as an
 * AttributeVisitor on their code attributes (so it can run its PartialEvaluator
 * and ReferenceEscapeChecker), and finally as an InstructionVisitor on its
 * instructions (to actually mark the parameters).
 *
 * @see SideEffectClassChecker
 * @see SideEffectClassMarker
 * @author Eric Lafortune
 */
public class ParameterEscapeMarker
extends      SimplifiedVisitor
implements   MemberVisitor,
             AttributeVisitor,
             InstructionVisitor,
             ConstantVisitor,
             ParameterVisitor
{
    //*
    private static final boolean DEBUG = false;
    /*/
    private static       boolean DEBUG = System.getProperty("pem") != null;
    //*/


    private final MutableBoolean         repeatTrigger;
    private final PartialEvaluator       partialEvaluator;
    private final boolean                runPartialEvaluator;
    private final ReferenceEscapeChecker referenceEscapeChecker;
    private final boolean                runReferenceEscapeChecker;

    private final MemberVisitor parameterMarker = new AllParameterVisitor(true, this);

    // Parameters and values for visitor methods.
    private Method  referencingMethod;
    private int     referencingOffset;
    private int     referencingPopCount;
    private boolean isReturnValueEscaping;
    private boolean isReturnValueModified;


    /**
     * Creates a new ParameterEscapeMarker.
     */
    public ParameterEscapeMarker(MutableBoolean repeatTrigger)
    {
        this(repeatTrigger,
             new BasicValueFactory());
    }


    /**
     * Creates a new ParameterEscapeMarker.
     */
    public ParameterEscapeMarker(MutableBoolean repeatTrigger,
                                 ValueFactory   valueFactory)
    {
        this(repeatTrigger,
             valueFactory,
             new ReferenceTracingValueFactory(valueFactory));
    }


    /**
     * Creates a new ParameterEscapeMarker.
     */
    public ParameterEscapeMarker(MutableBoolean               repeatTrigger,
                                 ValueFactory                 valueFactory,
                                 ReferenceTracingValueFactory tracingValueFactory)
    {
        this(repeatTrigger,
             new PartialEvaluator(tracingValueFactory,
                                  new ParameterTracingInvocationUnit(new BasicInvocationUnit(tracingValueFactory)),
                                  true,
                                  tracingValueFactory),
             true);
    }


    /**
     * Creates a new ParameterEscapeMarker.
     */
    public ParameterEscapeMarker(MutableBoolean   repeatTrigger,
                                 PartialEvaluator partialEvaluator,
                                 boolean          runPartialEvaluator)
    {
        this(repeatTrigger,
             partialEvaluator,
             runPartialEvaluator,
             new ReferenceEscapeChecker(partialEvaluator, false),
             true);
    }


    /**
     * Creates a new ParameterEscapeMarker.
     */
    public ParameterEscapeMarker(MutableBoolean         repeatTrigger,
                                 PartialEvaluator       partialEvaluator,
                                 boolean                runPartialEvaluator,
                                 ReferenceEscapeChecker referenceEscapeChecker,
                                 boolean                runReferenceEscapeChecker)
    {
        this.repeatTrigger             = repeatTrigger;
        this.partialEvaluator          = partialEvaluator;
        this.runPartialEvaluator       = runPartialEvaluator;
        this.referenceEscapeChecker    = referenceEscapeChecker;
        this.runReferenceEscapeChecker = runReferenceEscapeChecker;
    }


    // Implementations for MemberVisitor.

    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
    {
        int accessFlags = programMethod.getAccessFlags();

        // Is it a native method?
        if ((accessFlags & ClassConstants.ACC_NATIVE) != 0)
        {
            // Mark all parameters.
            markModifiedParameters(programMethod, -1L);
            markEscapingParameters(programMethod, -1L);
            markReturnedParameters(programMethod, -1L);
            markAnythingModified(programMethod);
        }
    }


    // Implementations for AttributeVisitor.

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


    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
    {
        // Evaluate the code.
        if (runPartialEvaluator)
        {
            partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
        }

        if (runReferenceEscapeChecker)
        {
            referenceEscapeChecker.visitCodeAttribute(clazz, method, codeAttribute);
        }

        if (DEBUG)
        {
            // These results are not complete yet, since this class must still
            // be called as an InstructionVisitor.
            System.out.println("ParameterEscapeMarker: [" + clazz.getName() + "." + method.getName(clazz) + method.getDescriptor(clazz) + "]");

            int parameterCount =
                ClassUtil.internalMethodParameterCount(method.getDescriptor(clazz),
                                                       method.getAccessFlags());

            for (int index = 0; index < parameterCount; index++)
            {
                System.out.println("  " +
//                                   (hasParameterEscaped(method, index) ? 'e' : '.') +
                                   (isParameterEscaping(method, index) ? 'E' : '.') +
                                   (isParameterReturned(method, index) ? 'R' : '.') +
                                   (isParameterModified(method, index) ? 'M' : '.') +
                                   " P" + index);
            }

            System.out.println("  " +
                               (returnsExternalValues(method) ? 'X' : '.') +
                               "   Return value");
        }
    }


    // 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 InstructionConstants.OP_AASTORE:
                // Mark array parameters whose element is modified.
                markModifiedParameters(method,
                                       offset,
                                       simpleInstruction.stackPopCount(clazz) - 1);

                // Mark reference values that are put in the array.
                markEscapingParameters(method, offset, 0);
                break;

            case InstructionConstants.OP_IASTORE:
            case InstructionConstants.OP_LASTORE:
            case InstructionConstants.OP_FASTORE:
            case InstructionConstants.OP_DASTORE:
            case InstructionConstants.OP_BASTORE:
            case InstructionConstants.OP_CASTORE:
            case InstructionConstants.OP_SASTORE:
                // Mark array parameters whose element is modified.
                markModifiedParameters(method,
                                       offset,
                                       simpleInstruction.stackPopCount(clazz) - 1);
                break;

            case InstructionConstants.OP_ARETURN:
                // Mark returned reference values.
                markReturnedParameters(clazz, method, offset, 0);
                break;

            case InstructionConstants.OP_ATHROW:
                // Mark the escaping reference values.
                markEscapingParameters(method, offset, 0);
                break;
        }
    }


    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
    {
        switch (constantInstruction.opcode)
        {
            case InstructionConstants.OP_LDC:
            case InstructionConstants.OP_LDC_W:
            case InstructionConstants.OP_NEW:
            case InstructionConstants.OP_ANEWARRAY:
            case InstructionConstants.OP_MULTIANEWARRAY:
            case InstructionConstants.OP_GETSTATIC:
                // Mark possible modifications due to initializers.
                referencingMethod = method;
                referencingOffset = offset;
                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                break;

            case InstructionConstants.OP_PUTSTATIC:
                // Mark some global modification.
                markAnythingModified(method);

                // Mark reference values that are put in the field.
                markEscapingParameters(method, offset, 0);
                break;

            case InstructionConstants.OP_PUTFIELD:
                // Mark reference parameters whose field is modified.
                markModifiedParameters(method,
                                       offset,
                                       constantInstruction.stackPopCount(clazz) - 1);

                // Mark reference values that are put in the field.
                markEscapingParameters(method, offset, 0);
                break;

            case InstructionConstants.OP_INVOKEVIRTUAL:
            case InstructionConstants.OP_INVOKESPECIAL:
            case InstructionConstants.OP_INVOKESTATIC:
            case InstructionConstants.OP_INVOKEINTERFACE:
            case InstructionConstants.OP_INVOKEDYNAMIC:
                // Mark reference parameters that are modified as parameters
                // of the invoked method.
                // Mark reference values that are escaping as parameters
                // of the invoked method.
                // Mark escaped reference parameters in the invoked method.
                referencingMethod   = method;
                referencingOffset   = offset;
                referencingPopCount = constantInstruction.stackPopCount(clazz);
                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                break;
        }
    }


    // Implementations for ConstantVisitor.

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


    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
    {
        Clazz referencedClass = stringConstant.referencedClass;

        // If a static initializer may modify anything, so does the referencing
        // method.
        if (referencedClass == null ||
            SideEffectClassChecker.mayHaveSideEffects(clazz,
                                                      referencedClass))
        {
            markAnythingModified(referencingMethod);
        }
    }


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

        // If a static initializer may modify anything, so does the referencing
        // method.
        if (referencedClass == null ||
            SideEffectClassChecker.mayHaveSideEffects(clazz,
                                                      referencedClass))
        {
            markAnythingModified(referencingMethod);
        }
    }


    public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant)
    {
        markAnythingModified(referencingMethod);
    }


    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
    {
        clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this);
    }


    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant)
    {
        Method referencedMethod = (Method)refConstant.referencedMember;

        // If the referenced method or a static initializer may modify anything,
        // so does the referencing method.
        if (referencedMethod == null ||
            modifiesAnything(referencedMethod) ||
            SideEffectClassChecker.mayHaveSideEffects(clazz,
                                                      refConstant.referencedClass,
                                                      referencedMethod))
        {
            markAnythingModified(referencingMethod);
        }

        // Do we know the invoked method?
        if (referencedMethod == null)
        {
            // Mark all parameters of the invoking method that are passed to
            // the invoked method, since they may escape or or be modified
            // there.
            for (int parameterOffset = 0; parameterOffset < referencingPopCount; parameterOffset++)
            {
                int stackEntryIndex = referencingPopCount - parameterOffset - 1;

                markEscapingParameters(referencingMethod,
                                       referencingOffset,
                                       stackEntryIndex);

                markModifiedParameters(referencingMethod,
                                       referencingOffset,
                                       stackEntryIndex);
            }
        }
        else
        {
            // Remember whether the return value of the method is escaping or
            // modified later on.
            isReturnValueEscaping =
                referenceEscapeChecker.isInstanceEscaping(referencingOffset);

            isReturnValueModified =
                referenceEscapeChecker.isInstanceModified(referencingOffset);

            // Mark parameters of the invoking method that are passed to the
            // invoked method and escaping or modified there.
            refConstant.referencedMemberAccept(parameterMarker);
        }
    }


    // Implementations for ParameterVisitor.

    public void visitParameter(Clazz clazz, Member member, int parameterIndex, int parameterCount, int parameterOffset, int parameterSize, String parameterType, Clazz referencedClass)
    {
        if (!ClassUtil.isInternalPrimitiveType(parameterType.charAt(0)))
        {
            Method method = (Method)member;

            // Is the parameter escaping from the method,
            // or is it returned and then escaping?
            if (isParameterEscaping(method, parameterIndex) ||
                (isParameterReturned(method, parameterIndex) &&
                 isReturnValueEscaping))
            {
                markEscapingParameters(referencingMethod,
                                       referencingOffset,
                                       parameterSize - parameterOffset - 1);
            }

            // Is the parameter being modified in the method.
            // or is it returned and then modified?
            if (isParameterModified(method, parameterIndex) ||
                (isParameterReturned(method, parameterIndex) &&
                 isReturnValueModified))
            {
                markModifiedParameters(referencingMethod,
                                       referencingOffset,
                                       parameterSize - parameterOffset - 1);
            }
        }
    }


    // Small utility methods.

    /**
     * Marks the producing reference parameters (and the classes) of the
     * specified stack entry at the given instruction offset.
     */
    private void markEscapingParameters(Method method,
                                        int    consumerOffset,
                                        int    stackEntryIndex)
    {
        TracedStack stackBefore = partialEvaluator.getStackBefore(consumerOffset);
        Value       stackEntry  = stackBefore.getTop(stackEntryIndex);

        if (stackEntry.computationalType() == Value.TYPE_REFERENCE)
        {
            ReferenceValue referenceValue = stackEntry.referenceValue();

            // The null reference value may not have a trace value.
            if (referenceValue.isNull() != Value.ALWAYS)
            {
                markEscapingParameters(method, referenceValue);
            }
        }
    }


    /**
     * Marks the producing parameters (and the classes) of the given
     * reference value.
     */
    private void markEscapingParameters(Method         method,
                                        ReferenceValue referenceValue)
    {
        TracedReferenceValue   tracedReferenceValue = (TracedReferenceValue)referenceValue;
        InstructionOffsetValue producers            = tracedReferenceValue.getTraceValue().instructionOffsetValue();

        int producerCount = producers.instructionOffsetCount();
        for (int index = 0; index < producerCount; index++)
        {
            if (producers.isMethodParameter(index))
            {
                // We know exactly which parameter is escaping.
                markParameterEscaping(method, producers.methodParameter(index));
            }
        }
    }


    /**
     * Marks the given parameter as escaping from the given method.
     */
    private void markParameterEscaping(Method method, int parameterIndex)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        if (!methodOptimizationInfo.isParameterEscaping(parameterIndex) &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setParameterEscaping(parameterIndex);

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.isParameterEscaping(parameterIndex))
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Marks the given parameters as escaping from the given method.
     */
    private void markEscapingParameters(Method method, long escapingParameters)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        long oldEscapingParameters =
            methodOptimizationInfo.getEscapingParameters();

        if ((~oldEscapingParameters & escapingParameters) != 0 &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).updateEscapingParameters(escapingParameters);

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.getEscapingParameters() != oldEscapingParameters)
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Returns whether the given parameter is escaping from the given method.
     */
    public static boolean isParameterEscaping(Method method, int parameterIndex)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).isParameterEscaping(parameterIndex);
    }


    /**
     * Returns which parameters are escaping from the given method.
     */
    public static long getEscapingParameters(Method method)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).getEscapingParameters();
    }


    /**
     * Marks the method and the returned reference parameters of the specified
     * stack entry at the given instruction offset.
     */
    private void markReturnedParameters(Clazz  clazz,
                                        Method method,
                                        int    returnOffset,
                                        int    stackEntryIndex)
    {
        TracedStack stackBefore = partialEvaluator.getStackBefore(returnOffset);
        Value       stackEntry  = stackBefore.getTop(stackEntryIndex);

        if (stackEntry.computationalType() == Value.TYPE_REFERENCE)
        {
            ReferenceValue referenceValue = stackEntry.referenceValue();

            // The null reference value may not have a trace value.
            if (referenceValue.isNull() != Value.ALWAYS &&
                mayReturnType(clazz, method, referenceValue))
            {
                markReturnedParameters(method, referenceValue);
            }
        }
    }


    /**
     * Marks the method and the producing parameters of the given reference
     * value.
     */
    private void markReturnedParameters(Method         method,
                                        ReferenceValue referenceValue)
    {
        TracedReferenceValue   tracedReferenceValue = (TracedReferenceValue)referenceValue;
        InstructionOffsetValue producers            = tracedReferenceValue.getTraceValue().instructionOffsetValue();

        int producerCount = producers.instructionOffsetCount();
        for (int index = 0; index < producerCount; index++)
        {
            if (producers.isMethodParameter(index))
            {
                // We know exactly which parameter is returned.
                markParameterReturned(method, producers.methodParameter(index));
            }
            else if (producers.isFieldValue(index))
            {
                markReturnsExternalValues(method);
            }
            else if (producers.isNewinstance(index) ||
                     producers.isExceptionHandler(index))
            {
                markReturnsNewInstances(method);
            }
        }
    }


    /**
     * Marks the given parameter as returned from the given method.
     */
    private void markParameterReturned(Method method, int parameterIndex)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        if (!methodOptimizationInfo.returnsParameter(parameterIndex) &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setParameterReturned(parameterIndex);

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.returnsParameter(parameterIndex))
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Marks the given parameters as returned from the given method.
     */
    private void markReturnedParameters(Method method, long returnedParameters)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        long oldReturnedParameters =
            methodOptimizationInfo.getReturnedParameters();

        if ((~oldReturnedParameters & returnedParameters) != 0 &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).updateReturnedParameters(returnedParameters);

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.getReturnedParameters() != oldReturnedParameters)
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Returns whether the given parameter is returned from the given method.
     */
    public static boolean isParameterReturned(Method method, int parameterIndex)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).returnsParameter(parameterIndex);
    }


    /**
     * Returns which parameters are returned from the given method.
     */
    public static long getReturnedParameters(Method method)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).getReturnedParameters();
    }


    /**
     * Marks that the given method returns new instances (created inside the
     * method).
     */
    private void markReturnsNewInstances(Method method)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        if (!methodOptimizationInfo.returnsNewInstances() &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setReturnsNewInstances();

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.returnsNewInstances())
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Returns whether the given method returns new instances (created inside
     * the method).
     */
    public static boolean returnsNewInstances(Method method)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).returnsNewInstances();
    }


    /**
     * Marks that the given method returns external reference values (not
     * parameter or new instance).
     */
    private void markReturnsExternalValues(Method method)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        if (!methodOptimizationInfo.returnsExternalValues() &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setReturnsExternalValues();

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.returnsExternalValues())
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Returns whether the given method returns external reference values
     * (not parameter or new instance).
     */
    public static boolean returnsExternalValues(Method method)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).returnsExternalValues();
    }


    /**
     * Returns whether the given method may return the given type of reference
     * value
     */
    private boolean mayReturnType(Clazz          clazz,
                                  Method         method,
                                  ReferenceValue referenceValue)
    {
        String returnType =
            ClassUtil.internalMethodReturnType(method.getDescriptor(clazz));

        Clazz[] referencedClasses = method instanceof ProgramMethod ?
            ((ProgramMethod)method).referencedClasses :
            ((LibraryMethod)method).referencedClasses;

        Clazz referencedClass =
            referencedClasses == null ||
            !ClassUtil.isInternalClassType(returnType) ? null :
                referencedClasses[referencedClasses.length - 1];

        return referenceValue.instanceOf(returnType,
                                         referencedClass) != Value.NEVER;
    }


    /**
     * Marks the producing reference parameters of the specified stack entry at
     * the given instruction offset.
     */
    private void markModifiedParameters(Method method,
                                        int    offset,
                                        int    stackEntryIndex)
    {
        TracedStack stackBefore = partialEvaluator.getStackBefore(offset);
        Value       stackEntry  = stackBefore.getTop(stackEntryIndex);

        if (stackEntry.computationalType() == Value.TYPE_REFERENCE)
        {
            ReferenceValue referenceValue = stackEntry.referenceValue();

            // The null reference value may not have a trace value.
            if (referenceValue.isNull() != Value.ALWAYS)
            {
                markModifiedParameters(method, referenceValue);
            }
        }
    }


    /**
     * Marks the producing parameters of the given reference value.
     */
    private void markModifiedParameters(Method         method,
                                        ReferenceValue referenceValue)
    {
        TracedReferenceValue   tracedReferenceValue = (TracedReferenceValue)referenceValue;
        InstructionOffsetValue producers            = tracedReferenceValue.getTraceValue().instructionOffsetValue();

        int producerCount = producers.instructionOffsetCount();
        for (int index = 0; index < producerCount; index++)
        {
            if (producers.isMethodParameter(index))
            {
                // We know exactly which parameter is being modified.
                markParameterModified(method, producers.methodParameter(index));
            }
            else if (!producers.isNewinstance(index) &&
                     !producers.isExceptionHandler(index))
            {
                // If some unknown instance is modified, any escaping parameters
                // may be modified.
                markModifiedParameters(method, getEscapingParameters(method));
                markAnythingModified(method);
            }
        }
    }


    /**
     * Marks the given parameter as modified by the given method.
     */
    private void markParameterModified(Method method, int parameterIndex)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        if (!methodOptimizationInfo.isParameterModified(parameterIndex) &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setParameterModified(parameterIndex);

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.isParameterModified(parameterIndex))
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Marks the given parameters as modified by the given method.
     */
    private void markModifiedParameters(Method method, long modifiedParameters)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        long oldModifiedParameters =
            methodOptimizationInfo.getModifiedParameters();

        if ((~oldModifiedParameters & modifiedParameters) != 0 &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).updateModifiedParameters(modifiedParameters);

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.getModifiedParameters() != oldModifiedParameters)
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Returns whether the given parameter is modified by the given method.
     */
    public static boolean isParameterModified(Method method, int parameterIndex)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).isParameterModified(parameterIndex);
    }


    /**
     * Returns which parameters are modified by the given method.
     */
    public static long getModifiedParameters(Method method)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).getModifiedParameters();
    }


    /**
     * Marks that anything may be modified by the given method.
     */
    private void markAnythingModified(Method method)
    {
        MethodOptimizationInfo methodOptimizationInfo =
            MethodOptimizationInfo.getMethodOptimizationInfo(method);

        if (!methodOptimizationInfo.modifiesAnything() &&
            methodOptimizationInfo instanceof ProgramMethodOptimizationInfo)
        {
            ((ProgramMethodOptimizationInfo)methodOptimizationInfo).setModifiesAnything();

            // Trigger the repeater if the setter has changed the value.
            if (methodOptimizationInfo.modifiesAnything())
            {
                repeatTrigger.set();
            }
        }
    }


    /**
     * Returns whether anything may be modified by the given method. This takes
     * into account the side effects of static initializers, except the static
     * initializer of the invoked method (because it is better checked
     * explicitly as a function of the referencing class).
     *
     * @see SideEffectClassChecker#mayHaveSideEffects(Clazz, Clazz, Member)
     */
    public static boolean modifiesAnything(Method method)
    {
        return MethodOptimizationInfo.getMethodOptimizationInfo(method).modifiesAnything();
    }
}