/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003,2004 University of Maryland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba;

import org.apache.bcel.generic.AASTORE;
import org.apache.bcel.generic.ARETURN;
import org.apache.bcel.generic.ArrayInstruction;
import org.apache.bcel.generic.CHECKCAST;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldInstruction;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.PUTFIELD;
import org.apache.bcel.generic.PUTSTATIC;

public abstract class ResourceValueFrameModelingVisitor extends AbstractFrameModelingVisitor<ResourceValue, ResourceValueFrame> {
    public ResourceValueFrameModelingVisitor(ConstantPoolGen cpg) {
        super(cpg);
    }

    @Override
    public ResourceValue getDefaultValue() {
        return ResourceValue.notInstance();
    }

    /**
     * Subclasses must override this to model the effect of the given
     * instruction on the current frame.
     */
    public abstract void transferInstruction(InstructionHandle handle, BasicBlock basicBlock) throws DataflowAnalysisException;

    // Things to do:
    // Automatically detect when resource instances escape:
    // - putfield, putstatic
    // - parameters to invoke, but subclasses may override
    // - aastore; (conservative, since the dest array may not itself escape)
    // - return (areturn)

    private void handleFieldStore(FieldInstruction ins) {
        try {
            // If the resource instance is stored in a field, then it escapes
            ResourceValueFrame frame = getFrame();
            ResourceValue topValue = frame.getTopValue();
            if (topValue.equals(ResourceValue.instance())) {
                frame.setStatus(ResourceValueFrame.ESCAPED);
            }
        } catch (DataflowAnalysisException e) {
            throw new InvalidBytecodeException("Stack underflow", e);
        }

        handleNormalInstruction(ins);
    }

    @Override
    public void visitPUTFIELD(PUTFIELD putfield) {
        handleFieldStore(putfield);
    }

    private void handleArrayStore(ArrayInstruction ins) {
        try {
            // If the resource instance is stored in an array, then we consider
            // it as having escaped. This is conservative; ideally we would
            // check whether this array is a field or gets passed out of the
            // method.
            ResourceValueFrame frame = getFrame();
            ResourceValue topValue = frame.getTopValue();
            if (topValue.equals(ResourceValue.instance())) {
                frame.setStatus(ResourceValueFrame.ESCAPED);
            }
        } catch (DataflowAnalysisException e) {
            throw new InvalidBytecodeException("Stack underflow", e);
        }
        handleNormalInstruction(ins);
    }

    @Override
    public void visitAASTORE(AASTORE arr) {
        handleArrayStore(arr);
    }

    @Override
    public void visitPUTSTATIC(PUTSTATIC putstatic) {
        handleFieldStore(putstatic);
    }

    /**
     * Override this to check for methods that it is legal to pass the instance
     * to without the instance escaping. By default, we consider all methods to
     * be possible escape routes.
     *
     * @param inv
     *            the InvokeInstruction to which the resource instance is passed
     *            as an argument
     * @param instanceArgNum
     *            the first argument the instance is passed in
     */
    protected boolean instanceEscapes(InvokeInstruction inv, int instanceArgNum) {
        return true;
    }

    private void handleInvoke(InvokeInstruction inv) {
        ResourceValueFrame frame = getFrame();
        int numSlots = frame.getNumSlots();
        int numConsumed = getNumWordsConsumed(inv);

        // See if the resource instance is passed as an argument
        int instanceArgNum = -1;
        for (int i = numSlots - numConsumed, argCount = 0; i < numSlots; ++i, ++argCount) {
            ResourceValue value = frame.getValue(i);
            if (value.equals(ResourceValue.instance())) {
                instanceArgNum = argCount;
                break;
            }
        }

        if (instanceArgNum >= 0 && instanceEscapes(inv, instanceArgNum)) {
            frame.setStatus(ResourceValueFrame.ESCAPED);
        }

        handleNormalInstruction(inv);
    }

    @Override
    public void visitCHECKCAST(CHECKCAST obj) {
        try {
            ResourceValueFrame frame = getFrame();
            ResourceValue topValue;

            topValue = frame.getTopValue();

            if (topValue.equals(ResourceValue.instance())) {
                frame.setStatus(ResourceValueFrame.ESCAPED);
            }
        } catch (DataflowAnalysisException e) {
            AnalysisContext.logError("Analysis error", e);
        }
    }

    @Override
    public void visitINVOKEVIRTUAL(INVOKEVIRTUAL inv) {
        handleInvoke(inv);
    }

    @Override
    public void visitINVOKEINTERFACE(INVOKEINTERFACE inv) {
        handleInvoke(inv);
    }

    @Override
    public void visitINVOKESPECIAL(INVOKESPECIAL inv) {
        handleInvoke(inv);
    }

    @Override
    public void visitINVOKESTATIC(INVOKESTATIC inv) {
        handleInvoke(inv);
    }

    @Override
    public void visitARETURN(ARETURN ins) {
        try {
            ResourceValueFrame frame = getFrame();
            ResourceValue topValue = frame.getTopValue();
            if (topValue.equals(ResourceValue.instance())) {
                frame.setStatus(ResourceValueFrame.ESCAPED);
            }
        } catch (DataflowAnalysisException e) {
            throw new InvalidBytecodeException("Stack underflow", e);
        }

        handleNormalInstruction(ins);
    }

}

