/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003-2005 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.type;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.annotation.CheckForNull;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.LocalVariableTypeTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ATHROW;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.ExceptionThrower;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;

import edu.umd.cs.findbugs.Analyze;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.AnalysisFeatures;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.Dataflow;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.DepthFirstSearch;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.EdgeTypes;
import edu.umd.cs.findbugs.ba.FrameDataflowAnalysis;
import edu.umd.cs.findbugs.ba.Hierarchy;
import edu.umd.cs.findbugs.ba.Hierarchy2;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.ObjectTypeFactory;
import edu.umd.cs.findbugs.ba.RepositoryLookupFailureCallback;
import edu.umd.cs.findbugs.ba.SignatureConverter;
import edu.umd.cs.findbugs.ba.generic.GenericSignatureParser;
import edu.umd.cs.findbugs.ba.generic.GenericUtilities;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;

/**
 * A forward dataflow analysis to determine the types of all values in the Java
 * stack frame at all points in a Java method. The values include local
 * variables and values on the Java operand stack.
 * <p/>
 * <p>
 * As a side effect, the analysis computes the exception set throwable on each
 * exception edge in the CFG. This information can be used to prune infeasible
 * exception edges, and mark exception edges which propagate only implicit
 * exceptions.
 *
 * @author David Hovemeyer
 * @see Dataflow
 * @see edu.umd.cs.findbugs.ba.DataflowAnalysis
 * @see TypeFrame
 */
public class TypeAnalysis extends FrameDataflowAnalysis<Type, TypeFrame> implements EdgeTypes {

    public static final boolean DEBUG = SystemProperties.getBoolean("ta.debug");

    /**
     * Force computation of accurate exceptions.
     */
    public static final boolean FORCE_ACCURATE_EXCEPTIONS = SystemProperties.getBoolean("ta.accurateExceptions");

    /**
     * Repository of information about thrown exceptions computed for a basic
     * block and its outgoing exception edges. It contains a result TypeFrame,
     * which is used to detect when the exception information needs to be
     * recomputed for the block.
     */
    private class CachedExceptionSet {
        private final TypeFrame result;

        private final ExceptionSet exceptionSet;

        private final Map<Edge, ExceptionSet> edgeExceptionMap;

        public CachedExceptionSet(TypeFrame result, ExceptionSet exceptionSet) {
            this.result = result;
            this.exceptionSet = exceptionSet;
            this.edgeExceptionMap = new HashMap<Edge, ExceptionSet>();
        }

        public boolean isUpToDate(TypeFrame result) {
            return this.result.equals(result);
        }

        public ExceptionSet getExceptionSet() {
            return exceptionSet;
        }

        public void setEdgeExceptionSet(Edge edge, ExceptionSet exceptionSet) {
            edgeExceptionMap.put(edge, exceptionSet);
        }

        public ExceptionSet getEdgeExceptionSet(Edge edge) {
            ExceptionSet edgeExceptionSet = edgeExceptionMap.get(edge);
            if (edgeExceptionSet == null) {
                edgeExceptionSet = exceptionSetFactory.createExceptionSet();
                edgeExceptionMap.put(edge, edgeExceptionSet);
            }
            return edgeExceptionSet;
        }
    }

    /**
     * Cached information about an instanceof check.
     */
    static class InstanceOfCheck {
        final ValueNumber valueNumber;

        final Type type;

        InstanceOfCheck(ValueNumber valueNumber, Type type) {
            this.valueNumber = valueNumber;
            this.type = type;
        }

        /**
         * @return Returns the valueNumber.
         */
        public ValueNumber getValueNumber() {
            return valueNumber;
        }

        /**
         * @return Returns the type.
         */
        public Type getType() {
            return type;
        }
    }

    protected MethodGen methodGen;

    private final Method method;

    protected CFG cfg;

    private final TypeMerger typeMerger;

    private final TypeFrameModelingVisitor visitor;

    private final Map<BasicBlock, CachedExceptionSet> thrownExceptionSetMap;

    private final RepositoryLookupFailureCallback lookupFailureCallback;

    private final ExceptionSetFactory exceptionSetFactory;

    private ValueNumberDataflow valueNumberDataflow;

    private final Map<BasicBlock, InstanceOfCheck> instanceOfCheckMap;

    /**
     * Constructor.
     *
     * @param method
     *            TODO
     * @param methodGen
     *            the MethodGen whose CFG we'll be analyzing
     * @param cfg
     *            the control flow graph
     * @param dfs
     *            DepthFirstSearch of the method
     * @param typeMerger
     *            object to merge types
     * @param visitor
     *            a TypeFrameModelingVisitor to use to model the effect of
     *            instructions
     * @param lookupFailureCallback
     *            lookup failure callback
     * @param exceptionSetFactory
     *            factory for creating ExceptionSet objects
     */
    public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg, DepthFirstSearch dfs, TypeMerger typeMerger,
            TypeFrameModelingVisitor visitor, RepositoryLookupFailureCallback lookupFailureCallback,
            ExceptionSetFactory exceptionSetFactory) {
        super(dfs);
        this.method = method;
        Code code = method.getCode();
        if (code == null) {
            throw new IllegalArgumentException(method.getName() + " has no code");
        }
        for (Attribute a : code.getAttributes()) {
            if (a instanceof LocalVariableTypeTable) {
                visitor.setLocalTypeTable((LocalVariableTypeTable) a);
            }
        }
        this.methodGen = methodGen;
        this.cfg = cfg;
        this.typeMerger = typeMerger;
        this.visitor = visitor;
        this.thrownExceptionSetMap = new HashMap<BasicBlock, CachedExceptionSet>();
        this.lookupFailureCallback = lookupFailureCallback;
        this.exceptionSetFactory = exceptionSetFactory;
        this.instanceOfCheckMap = new HashMap<BasicBlock, InstanceOfCheck>();
        if (DEBUG) {
            System.out.println("\n\nAnalyzing " + methodGen);
        }
    }

    /**
     * Constructor.
     *
     * @param method
     *            TODO
     * @param methodGen
     *            the MethodGen whose CFG we'll be analyzing
     * @param cfg
     *            the control flow graph
     * @param dfs
     *            DepthFirstSearch of the method
     * @param typeMerger
     *            object to merge types
     * @param lookupFailureCallback
     *            lookup failure callback
     * @param exceptionSetFactory
     *            factory for creating ExceptionSet objects
     */
    public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg, DepthFirstSearch dfs, TypeMerger typeMerger,
            RepositoryLookupFailureCallback lookupFailureCallback, ExceptionSetFactory exceptionSetFactory) {
        this(method, methodGen, cfg, dfs, typeMerger, new TypeFrameModelingVisitor(methodGen.getConstantPool(), typeMerger),
                lookupFailureCallback, exceptionSetFactory);
        if (TypeFrameModelingVisitor.DEBUG) {
            System.out.println(methodGen.getClassName() + "." + methodGen.getName() + " " + methodGen.getSignature());
        }
    }

    /**
     * Constructor which uses StandardTypeMerger.
     *
     * @param method
     *            TODO
     * @param methodGen
     *            the MethodGen whose CFG we'll be analyzing
     * @param cfg
     *            the control flow graph
     * @param dfs
     *            DepthFirstSearch of the method
     * @param lookupFailureCallback
     *            callback for Repository lookup failures
     * @param exceptionSetFactory
     *            factory for creating ExceptionSet objects
     */
    public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg, DepthFirstSearch dfs,
            RepositoryLookupFailureCallback lookupFailureCallback, ExceptionSetFactory exceptionSetFactory) {
        this(method, methodGen, cfg, dfs, new StandardTypeMerger(lookupFailureCallback, exceptionSetFactory),
                lookupFailureCallback, exceptionSetFactory);
    }

    /**
     * Set the ValueNumberDataflow for the method being analyzed. This is
     * optional; if set, it will be used to make instanceof instructions more
     * precise.
     *
     * @param valueNumberDataflow
     *            the ValueNumberDataflow
     */
    public void setValueNumberDataflow(ValueNumberDataflow valueNumberDataflow) {
        this.valueNumberDataflow = valueNumberDataflow;
        this.visitor.setValueNumberDataflow(valueNumberDataflow);
    }

    /**
     * Set the FieldStoreTypeDatabase. This can be used to get more accurate
     * types for values loaded from fields.
     *
     * @param database
     *            the FieldStoreTypeDatabase
     */
    public void setFieldStoreTypeDatabase(FieldStoreTypeDatabase database) {
        visitor.setFieldStoreTypeDatabase(database);
    }

    /**
     * Get the set of exceptions that can be thrown on given edge. This should
     * only be called after the analysis completes.
     *
     * @param edge
     *            the Edge
     * @return the ExceptionSet
     */
    public ExceptionSet getEdgeExceptionSet(Edge edge) {
        CachedExceptionSet cachedExceptionSet = thrownExceptionSetMap.get(edge.getSource());
        return cachedExceptionSet.getEdgeExceptionSet(edge);
    }

    @Override
    public TypeFrame createFact() {
        return new TypeFrame(methodGen.getMaxLocals());
    }

    @Override
    public void initEntryFact(TypeFrame result) {
        // Make the frame valid
        result.setValid();

        int slot = 0;

        // Clear the stack slots in the frame
        result.clearStack();

        // Add local for "this" pointer, if present
        if (!methodGen.isStatic()) {
            result.setValue(slot++, ObjectTypeFactory.getInstance(methodGen.getClassName()));
        }

        // [Added: Support for Generics]
        // Get a parser that reads the generic signature of the method and
        // can be used to get the correct GenericObjectType if an argument
        // has a class type
        Iterator<String> iter = GenericSignatureParser.getGenericSignatureIterator(method);

        // Add locals for parameters.
        // Note that long and double parameters need to be handled
        // specially because they occupy two locals.
        Type[] argumentTypes = methodGen.getArgumentTypes();
        for (Type argType : argumentTypes) {
            // Add special "extra" type for long or double params.
            // These occupy the slot before the "plain" type.
            if (argType.getType() == Constants.T_LONG) {
                result.setValue(slot++, TypeFrame.getLongExtraType());
            } else if (argType.getType() == Constants.T_DOUBLE) {
                result.setValue(slot++, TypeFrame.getDoubleExtraType());
            }

            // [Added: Support for Generics]
            String s = (iter == null || !iter.hasNext()) ? null : iter.next();
            if (s != null && (argType instanceof ObjectType || argType instanceof ArrayType)
                    && !(argType instanceof ExceptionObjectType)) {
                // replace with a generic version of the type
                try {
                    Type t = GenericUtilities.getType(s);
                    if (t != null) {
                        argType = t;
                    }
                } catch (RuntimeException e) {
                } // degrade gracefully
            }

            // Add the plain parameter type.
            result.setValue(slot++, argType);
        }

        // Set remaining locals to BOTTOM; this will cause any
        // uses of them to be flagged
        while (slot < methodGen.getMaxLocals()) {
            result.setValue(slot++, TypeFrame.getBottomType());
        }
    }

    @Override
    public void copy(TypeFrame source, TypeFrame dest) {
        dest.copyFrom(source);
    }

    @Override
    public void makeFactTop(TypeFrame fact) {
        fact.setTop();
    }

    @Override
    public boolean isFactValid(TypeFrame fact) {
        return fact.isValid();
    }

    @Override
    public boolean same(TypeFrame fact1, TypeFrame fact2) {
        return fact1.sameAs(fact2);
    }

    @Override
    public void transferInstruction(InstructionHandle handle, BasicBlock basicBlock, TypeFrame fact)
            throws DataflowAnalysisException {
        visitor.setFrameAndLocation(fact, new Location(handle, basicBlock));
        visitor.analyzeInstruction(handle.getInstruction());
    }

    @Override
    public void transfer(BasicBlock basicBlock, @CheckForNull InstructionHandle end, TypeFrame start, TypeFrame result)
            throws DataflowAnalysisException {
        visitor.startBasicBlock();

        super.transfer(basicBlock, end, start, result);

        // Compute thrown exception types
        computeThrownExceptionTypes(basicBlock, end, result);
        if (DEBUG) {
            System.out.println("After " + basicBlock.getFirstInstruction() + " -> " + basicBlock.getLastInstruction());
            System.out.println("    frame: " + result);
        }

        // If this block ends with an instanceof check,
        // update the cached information about it.
        instanceOfCheckMap.remove(basicBlock);
        if (visitor.isInstanceOfFollowedByBranch()) {
            InstanceOfCheck check = new InstanceOfCheck(visitor.getInstanceOfValueNumber(), visitor.getInstanceOfType());
            instanceOfCheckMap.put(basicBlock, check);
        }
    }

    private void computeThrownExceptionTypes(BasicBlock basicBlock, @CheckForNull InstructionHandle end, TypeFrame result)
            throws DataflowAnalysisException {

        // Do nothing if we're not computing propagated exceptions
        if (!(FORCE_ACCURATE_EXCEPTIONS || AnalysisContext.currentAnalysisContext().getBoolProperty(
                AnalysisFeatures.ACCURATE_EXCEPTIONS))) {
            return;
        }

        // Also, nothing to do if the block is not an exception thrower
        if (!basicBlock.isExceptionThrower()) {
            return;
        }

        // If cached results are up to date, don't recompute.
        CachedExceptionSet cachedExceptionSet = getCachedExceptionSet(basicBlock);
        if (cachedExceptionSet.isUpToDate(result)) {
            return;
        }

        // Figure out what exceptions can be thrown out
        // of the basic block, and mark each exception edge
        // with the set of exceptions which can be propagated
        // along the edge.

        int exceptionEdgeCount = 0;
        Edge lastExceptionEdge = null;

        for (Iterator<Edge> i = cfg.outgoingEdgeIterator(basicBlock); i.hasNext();) {
            Edge e = i.next();
            if (e.isExceptionEdge()) {
                exceptionEdgeCount++;
                lastExceptionEdge = e;
            }
        }

        if (exceptionEdgeCount == 0) {
            // System.out.println("Shouldn't all blocks have an exception edge");
            return;
        }
        // Compute exceptions that can be thrown by the
        // basic block.
        cachedExceptionSet = computeBlockExceptionSet(basicBlock, result);

        if (exceptionEdgeCount == 1) {
            cachedExceptionSet.setEdgeExceptionSet(lastExceptionEdge, cachedExceptionSet.getExceptionSet());
            return;
        }

        // For each outgoing exception edge, compute exceptions
        // that can be thrown. This assumes that the exception
        // edges are enumerated in decreasing order of priority.
        // In the process, this will remove exceptions from
        // the thrown exception set.
        ExceptionSet thrownExceptionSet = cachedExceptionSet.getExceptionSet();
        if (!thrownExceptionSet.isEmpty()) {
            thrownExceptionSet = thrownExceptionSet.duplicate();
        }
        for (Iterator<Edge> i = cfg.outgoingEdgeIterator(basicBlock); i.hasNext();) {
            Edge edge = i.next();
            if (edge.isExceptionEdge()) {
                cachedExceptionSet.setEdgeExceptionSet(edge, computeEdgeExceptionSet(edge, thrownExceptionSet));
            }
        }
    }

    @Override
    public void meetInto(TypeFrame fact, Edge edge, TypeFrame result) throws DataflowAnalysisException {
        BasicBlock basicBlock = edge.getTarget();

        if (fact.isValid()) {
            TypeFrame tmpFact = null;

            // Handling an exception?
            if (basicBlock.isExceptionHandler()) {
                tmpFact = modifyFrame(fact, null);

                // Special case: when merging predecessor facts for entry to
                // an exception handler, we clear the stack and push a
                // single entry for the exception object. That way, the locals
                // can still be merged.
                CodeExceptionGen exceptionGen = basicBlock.getExceptionGen();
                tmpFact.clearStack();

                // Determine the type of exception(s) caught.
                Type catchType = null;

                if (FORCE_ACCURATE_EXCEPTIONS
                        || AnalysisContext.currentAnalysisContext().getBoolProperty(AnalysisFeatures.ACCURATE_EXCEPTIONS)) {
                    try {
                        // Ideally, the exceptions that can be propagated
                        // on this edge has already been computed.
                        CachedExceptionSet cachedExceptionSet = getCachedExceptionSet(edge.getSource());
                        ExceptionSet edgeExceptionSet = cachedExceptionSet.getEdgeExceptionSet(edge);
                        if (!edgeExceptionSet.isEmpty()) {
                            // System.out.println("Using computed edge exception set!");
                            catchType = ExceptionObjectType.fromExceptionSet(edgeExceptionSet);
                        }
                    } catch (ClassNotFoundException e) {
                        lookupFailureCallback.reportMissingClass(e);
                    }
                }

                if (catchType == null) {
                    // No information about propagated exceptions, so
                    // pick a type conservatively using the handler catch type.
                    catchType = exceptionGen.getCatchType();
                    if (catchType == null)
                    {
                        catchType = Type.THROWABLE; // handle catches anything
                        // throwable
                    }
                }

                tmpFact.pushValue(catchType);
            }

            // See if we can make some types more precise due to
            // a successful instanceof check in the source block.
            if (valueNumberDataflow != null) {
                tmpFact = handleInstanceOfBranch(fact, tmpFact, edge);
            }

            if (tmpFact != null) {
                fact = tmpFact;
            }
        }

        mergeInto(fact, result);
    }

    private TypeFrame handleInstanceOfBranch(TypeFrame fact, TypeFrame tmpFact, Edge edge) {

        InstanceOfCheck check = instanceOfCheckMap.get(edge.getSource());
        if (check == null) {
            // System.out.println("no instanceof check for block " +
            // edge.getSource().getId());
            return tmpFact;
        }

        if (check.getValueNumber() == null) {
            // System.out.println("instanceof check for block " +
            // edge.getSource().getId() + " has no value number");
            return tmpFact;
        }

        ValueNumber instanceOfValueNumber = check.getValueNumber();
        ValueNumberFrame vnaFrame = valueNumberDataflow.getStartFact(edge.getTarget());
        if (!vnaFrame.isValid()) {
            return tmpFact;
        }

        Type instanceOfType = check.getType();
        if (!(instanceOfType instanceof ReferenceType || instanceOfType instanceof NullType)) {
            return tmpFact;
        }

        short branchOpcode = edge.getSource().getLastInstruction().getInstruction().getOpcode();

        int edgeType = edge.getType();
        int numSlots = Math.min(fact.getNumSlots(), vnaFrame.getNumSlots());

        if ((edgeType == EdgeTypes.IFCMP_EDGE && (branchOpcode == Constants.IFNE || branchOpcode == Constants.IFGT || branchOpcode == Constants.IFNULL))

                || (edgeType == EdgeTypes.FALL_THROUGH_EDGE && (branchOpcode == Constants.IFEQ || branchOpcode == Constants.IFLE || branchOpcode == Constants.IFNONNULL))) {
            // System.out.println("Successful check on edge " + edge);

            // Successful instanceof check.

            for (int i = 0; i < numSlots; ++i) {
                if (!vnaFrame.getValue(i).equals(instanceOfValueNumber)) {
                    continue;
                }

                Type checkedType = fact.getValue(i);
                if (!(checkedType instanceof ReferenceType)) {
                    continue;
                }

                // Only refine the type if the cast is feasible: i.e., a
                // downcast.
                // Otherwise, just set it to TOP.
                try {
                    boolean guaranteed = Hierarchy.isSubtype((ReferenceType) checkedType, (ReferenceType) instanceOfType);
                    if (guaranteed) {
                        continue;
                    }

                    boolean feasibleCheck = instanceOfType.equals(NullType.instance())
                            || Hierarchy.isSubtype((ReferenceType) instanceOfType, (ReferenceType) checkedType);

                    if (!feasibleCheck && instanceOfType instanceof ObjectType && checkedType instanceof ObjectType) {
                        double v = Analyze.deepInstanceOf(((ObjectType) instanceOfType).getClassName(),
                                ((ObjectType) checkedType).getClassName());
                        if (v > 0.0) {
                            feasibleCheck = true;
                        }
                    }
                    tmpFact = modifyFrame(fact, tmpFact);
                    if (feasibleCheck) {
                        tmpFact.setValue(i, instanceOfType);
                    } else {
                        tmpFact.setTop();
                        return tmpFact;
                    }
                } catch (ClassNotFoundException e) {
                    lookupFailureCallback.reportMissingClass(e);
                    return tmpFact;
                }
            }
        } else if (!instanceOfType.equals(NullType.instance())) {

            for (int i = 0; i < numSlots; ++i) {
                if (!vnaFrame.getValue(i).equals(instanceOfValueNumber)) {
                    continue;
                }

                Type checkedType = fact.getValue(i);
                if (!(checkedType instanceof ReferenceType)) {
                    continue;
                }
                try {
                    boolean guaranteed = Hierarchy.isSubtype((ReferenceType) checkedType, (ReferenceType) instanceOfType);
                    if (!guaranteed) {
                        continue;
                    }
                    tmpFact = modifyFrame(fact, tmpFact);
                    tmpFact.setTop();
                    return tmpFact;
                } catch (ClassNotFoundException e) {
                    lookupFailureCallback.reportMissingClass(e);
                    return tmpFact;
                }
            }
        }

        return tmpFact;
    }

    @Override
    protected void mergeValues(TypeFrame otherFrame, TypeFrame resultFrame, int slot) throws DataflowAnalysisException {

        Type type2 = resultFrame.getValue(slot);
        Type type1 = otherFrame.getValue(slot);
        Type value = typeMerger.mergeTypes(type2, type1);
        resultFrame.setValue(slot, value);

        // Result type is exact IFF types are identical and both are exact

        boolean typesAreIdentical = type1.equals(type2);

        boolean bothExact = resultFrame.isExact(slot) && otherFrame.isExact(slot);

        resultFrame.setExact(slot, typesAreIdentical && bothExact);
    }

    /**
     * Get the cached set of exceptions that can be thrown from given basic
     * block. If this information hasn't been computed yet, then an empty
     * exception set is returned.
     *
     * @param basicBlock
     *            the block to get the cached exception set for
     * @return the CachedExceptionSet for the block
     */
    private CachedExceptionSet getCachedExceptionSet(BasicBlock basicBlock) {
        CachedExceptionSet cachedExceptionSet = thrownExceptionSetMap.get(basicBlock);
        if (cachedExceptionSet == null) {
            // When creating the cached exception type set for the first time:
            // - the block result is set to TOP, so it won't match
            // any block result that has actually been computed
            // using the analysis transfer function
            // - the exception set is created as empty (which makes it
            // return TOP as its common superclass)

            TypeFrame top = createFact();
            makeFactTop(top);
            cachedExceptionSet = new CachedExceptionSet(top, exceptionSetFactory.createExceptionSet());

            thrownExceptionSetMap.put(basicBlock, cachedExceptionSet);
        }

        return cachedExceptionSet;
    }

    /**
     * Compute the set of exceptions that can be thrown from the given basic
     * block. This should only be called if the existing cached exception set is
     * out of date.
     *
     * @param basicBlock
     *            the basic block
     * @param result
     *            the result fact for the block; this is used to determine
     *            whether or not the cached exception set is up to date
     * @return the cached exception set for the block
     */
    private CachedExceptionSet computeBlockExceptionSet(BasicBlock basicBlock, TypeFrame result) throws DataflowAnalysisException {

        ExceptionSet exceptionSet = computeThrownExceptionTypes(basicBlock);


        TypeFrame copyOfResult = createFact();
        copy(result, copyOfResult);

        CachedExceptionSet cachedExceptionSet = new CachedExceptionSet(copyOfResult, exceptionSet);
        thrownExceptionSetMap.put(basicBlock, cachedExceptionSet);

        return cachedExceptionSet;
    }

    /**
     * Based on the set of exceptions that can be thrown from the source basic
     * block, compute the set of exceptions that can propagate along given
     * exception edge. This method should be called for each outgoing exception
     * edge in sequence, so the caught exceptions can be removed from the thrown
     * exception set as needed.
     *
     * @param edge
     *            the exception edge
     * @param thrownExceptionSet
     *            current set of exceptions that can be thrown, taking earlier
     *            (higher priority) exception edges into account
     * @return the set of exceptions that can propagate along this edge
     */
    private ExceptionSet computeEdgeExceptionSet(Edge edge, ExceptionSet thrownExceptionSet) {

        if (thrownExceptionSet.isEmpty()) {
            return thrownExceptionSet;
        }
        ExceptionSet result = exceptionSetFactory.createExceptionSet();

        if (edge.getType() == UNHANDLED_EXCEPTION_EDGE) {
            // The unhandled exception edge always comes
            // after all of the handled exception edges.
            result.addAll(thrownExceptionSet);
            thrownExceptionSet.clear();
            return result;
        }

        BasicBlock handlerBlock = edge.getTarget();
        CodeExceptionGen handler = handlerBlock.getExceptionGen();
        ObjectType catchType = handler.getCatchType();

        if (Hierarchy.isUniversalExceptionHandler(catchType)) {
            result.addAll(thrownExceptionSet);
            thrownExceptionSet.clear();
        } else {
            // Go through the set of thrown exceptions.
            // Any that will DEFINITELY be caught be this handler, remove.
            // Any that MIGHT be caught, but won't definitely be caught,
            // remain.

            for (ExceptionSet.ThrownExceptionIterator i = thrownExceptionSet.iterator(); i.hasNext();) {
                // ThrownException thrownException = i.next();
                ObjectType thrownType = i.next();
                boolean explicit = i.isExplicit();

                if (DEBUG) {
                    System.out.println("\texception type " + thrownType + ", catch type " + catchType);
                }

                try {
                    if (Hierarchy.isSubtype(thrownType, catchType)) {
                        // Exception can be thrown along this edge
                        result.add(thrownType, explicit);

                        // And it will definitely be caught
                        i.remove();

                        if (DEBUG) {
                            System.out.println("\tException is subtype of catch type: " + "will definitely catch");
                        }
                    } else if (Hierarchy.isSubtype(catchType, thrownType)) {
                        // Exception possibly thrown along this edge
                        result.add(thrownType, explicit);

                        if (DEBUG) {
                            System.out.println("\tException is supertype of catch type: " + "might catch");
                        }
                    }
                } catch (ClassNotFoundException e) {
                    // As a special case, if a class hierarchy lookup
                    // fails, then we will conservatively assume that the
                    // exception in question CAN, but WON'T NECESSARILY
                    // be caught by the handler.
                    AnalysisContext.reportMissingClass(e);
                    result.add(thrownType, explicit);
                }
            }
        }

        return result;
    }

    /**
     * Compute the set of exception types that can be thrown by given basic
     * block.
     *
     * @param basicBlock
     *            the basic block
     * @return the set of exceptions that can be thrown by the block
     */
    private ExceptionSet computeThrownExceptionTypes(BasicBlock basicBlock) throws
    DataflowAnalysisException {

        ExceptionSet exceptionTypeSet = exceptionSetFactory.createExceptionSet();
        InstructionHandle pei = basicBlock.getExceptionThrower();
        Instruction ins = pei.getInstruction();

        // Get the exceptions that BCEL knows about.
        // Note that all of these are unchecked.
        ExceptionThrower exceptionThrower = (ExceptionThrower) ins;
        Class<?>[] exceptionList = exceptionThrower.getExceptions();
        for (Class<?> aExceptionList : exceptionList) {
            exceptionTypeSet.addImplicit(ObjectTypeFactory.getInstance(aExceptionList.getName()));
        }

        // Assume that an Error may be thrown by any instruction.
        exceptionTypeSet.addImplicit(Hierarchy.ERROR_TYPE);

        if (ins instanceof ATHROW) {
            // For ATHROW instructions, we generate *two* blocks
            // for which the ATHROW is an exception thrower.
            //
            // - The first, empty basic block, does the null check
            // - The second block, which actually contains the ATHROW,
            // throws the object on the top of the operand stack
            //
            // We make a special case of the block containing the ATHROW,
            // by removing all of the implicit exceptions,
            // and using type information to figure out what is thrown.

            if (basicBlock.containsInstruction(pei)) {
                // This is the actual ATHROW, not the null check
                // and implicit exceptions.
                exceptionTypeSet.clear();

                // The frame containing the thrown value is the start fact
                // for the block, because ATHROW is guaranteed to be
                // the only instruction in the block.
                TypeFrame frame = getStartFact(basicBlock);

                // Check whether or not the frame is valid.
                // Sun's javac sometimes emits unreachable code.
                // For example, it will emit code that follows a JSR
                // subroutine call that never returns.
                // If the frame is invalid, then we can just make
                // a conservative assumption that anything could be
                // thrown at this ATHROW.
                if (!frame.isValid()) {
                    exceptionTypeSet.addExplicit(Type.THROWABLE);
                } else if (frame.getStackDepth() == 0) {
                    throw new IllegalStateException("empty stack " + " thrown by " + pei + " in "
                            + SignatureConverter.convertMethodSignature(methodGen));
                } else {

                    Type throwType = frame.getTopValue();
                    if (throwType instanceof ObjectType) {
                        exceptionTypeSet.addExplicit((ObjectType) throwType);
                    } else if (throwType instanceof ExceptionObjectType) {
                        exceptionTypeSet.addAll(((ExceptionObjectType) throwType).getExceptionSet());
                    } else {
                        // Not sure what is being thrown here.
                        // Be conservative.
                        if (DEBUG) {
                            System.out.println("Non object type " + throwType + " thrown by " + pei + " in "
                                    + SignatureConverter.convertMethodSignature(methodGen));
                        }
                        exceptionTypeSet.addExplicit(Type.THROWABLE);
                    }
                }
            }
        }

        // If it's an InvokeInstruction, add declared exceptions and
        // RuntimeException
        if (ins instanceof InvokeInstruction) {
            ConstantPoolGen cpg = methodGen.getConstantPool();

            InvokeInstruction inv = (InvokeInstruction) ins;
            ObjectType[] declaredExceptionList = Hierarchy2.findDeclaredExceptions(inv, cpg);
            if (declaredExceptionList == null) {
                // Couldn't find declared exceptions,
                // so conservatively assume it could thrown any checked
                // exception.
                if (DEBUG) {
                    System.out.println("Couldn't find declared exceptions for "
                            + SignatureConverter.convertMethodSignature(inv, cpg));
                }
                exceptionTypeSet.addExplicit(Hierarchy.EXCEPTION_TYPE);
            } else {
                for (ObjectType aDeclaredExceptionList : declaredExceptionList) {
                    exceptionTypeSet.addExplicit(aDeclaredExceptionList);
                }
            }

            exceptionTypeSet.addImplicit(Hierarchy.RUNTIME_EXCEPTION_TYPE);
        }

        if (DEBUG) {
            System.out.println(pei + " can throw " + exceptionTypeSet);
        }

        return exceptionTypeSet;
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + "(" + methodGen.getClassName() + "." + methodGen.getMethod().getName()
                + methodGen.getMethod().getSignature() + ")";
    }

    public boolean isImpliedByGenericTypes(ReferenceType t) {
        return visitor.isImpliedByGenericTypes(t);
    }

    // public static void main(String[] argv) throws Exception {
    // if (argv.length != 1) {
    // System.err.println("Usage: " + TypeAnalysis.class.getName() +
    // " <class file>");
    // System.exit(1);
    // }
    //
    // DataflowTestDriver<TypeFrame, TypeAnalysis> driver = new
    // DataflowTestDriver<TypeFrame, TypeAnalysis>() {
    // @Override
    // public Dataflow<TypeFrame, TypeAnalysis> createDataflow(ClassContext
    // classContext, Method method)
    // throws CFGBuilderException, DataflowAnalysisException {
    // return classContext.getTypeDataflow(method);
    // }
    // };
    //
    // driver.execute(argv[0]);
    // }
}
