/*
 * Bytecode analysis framework
 * Copyright (C) 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.npe;

import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.CodeException;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;

import edu.umd.cs.findbugs.BugAnnotation;
import edu.umd.cs.findbugs.FieldAnnotation;
import edu.umd.cs.findbugs.LocalVariableAnnotation;
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.AssertionMethods;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.CFGBuilderException;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.DominatorsAnalysis;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.EdgeTypes;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.MissingClassException;
import edu.umd.cs.findbugs.ba.PostDominatorsAnalysis;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefDataflow;
import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefSet;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;
import edu.umd.cs.findbugs.ba.vna.ValueNumberSourceInfo;
import edu.umd.cs.findbugs.bcel.generic.NullnessConversationInstruction;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.log.Profiler;

/**
 * A user-friendly front end for finding null pointer dereferences and redundant
 * null comparisions.
 *
 * @see IsNullValueAnalysis
 * @author David Hovemeyer
 */
public class NullDerefAndRedundantComparisonFinder {
    private static final boolean DEBUG = SystemProperties.getBoolean("fnd.debug");

    private static final boolean PRUNE_GUARANTEED_DEREFERENCES = SystemProperties.getBoolean("fnd.prune", true);

    private static final boolean DEBUG_DEREFS = SystemProperties.getBoolean("fnd.derefs.debug");

    private final ClassContext classContext;

    private final Method method;

    private final NullDerefAndRedundantComparisonCollector collector;

    private final boolean findGuaranteedDerefs;

    private final List<RedundantBranch> redundantBranchList;

    private final BitSet definitelySameBranchSet;

    private final BitSet definitelyDifferentBranchSet;

    private final BitSet undeterminedBranchSet;

    private final BitSet lineMentionedMultipleTimes;

    private IsNullValueDataflow invDataflow;

    private ValueNumberDataflow vnaDataflow;

    private UnconditionalValueDerefDataflow uvdDataflow;

    private final AssertionMethods assertionMethods;

    static {
        if (DEBUG)
            System.out.println("fnd.debug enabled");
    }

    /**
     * Constructor.
     *
     * @param classContext
     *            the ClassContext
     * @param method
     *            the method to analyze
     * @param collector
     *            the NullDerefAndRedundantComparisonCollector used to report
     *            null derefs and redundant null comparisons
     */
    public NullDerefAndRedundantComparisonFinder(ClassContext classContext, Method method,
            NullDerefAndRedundantComparisonCollector collector) {

        this.classContext = classContext;
        this.method = method;
        this.collector = collector;
        this.findGuaranteedDerefs = classContext.getAnalysisContext().getBoolProperty(
                AnalysisFeatures.TRACK_GUARANTEED_VALUE_DEREFS_IN_NULL_POINTER_ANALYSIS);
        this.lineMentionedMultipleTimes = classContext.linesMentionedMultipleTimes(method);

        this.redundantBranchList = new LinkedList<RedundantBranch>();
        this.definitelySameBranchSet = new BitSet();
        this.definitelyDifferentBranchSet = new BitSet();
        this.undeterminedBranchSet = new BitSet();
        this.assertionMethods = classContext.getAssertionMethods();
    }

    public void execute() {
        Profiler profiler = Global.getAnalysisCache().getProfiler();
        profiler.start(this.getClass());
        try {
            // Do the null-value analysis
            this.invDataflow = classContext.getIsNullValueDataflow(method);
            this.vnaDataflow = classContext.getValueNumberDataflow(method);
            if (findGuaranteedDerefs) {
                if (DEBUG_DEREFS) {
                    System.out.println("Checking for guaranteed derefs in "
                            + classContext.getClassDescriptor().getDottedClassName() + "." + method.getName()
                            + method.getSignature());
                }
                this.uvdDataflow = classContext.getUnconditionalValueDerefDataflow(method);
            }

            // Check method and report potential null derefs and
            // redundant null comparisons.
            examineBasicBlocks();
            if (findGuaranteedDerefs) {
                examineNullValues();
            }
            examineRedundantBranches();
        } catch (MissingClassException e) {
            AnalysisContext.reportMissingClass(e.getClassNotFoundException());
        } catch (CheckedAnalysisException e) {
            AnalysisContext.logError("Error while checking guaranteed derefs in "
                    + classContext.getClassDescriptor().getDottedClassName() + "." + method.getName() + method.getSignature(), e);
        } finally {
            profiler.end(this.getClass());
        }

    }

    /**
     * Examine basic blocks for null checks and potentially-redundant null
     * comparisons.
     *
     * @throws DataflowAnalysisException
     * @throws CFGBuilderException
     */
    private void examineBasicBlocks() throws DataflowAnalysisException, CFGBuilderException {
        // Look for null check blocks where the reference being checked
        // is definitely null, or null on some path
        Iterator<BasicBlock> bbIter = invDataflow.getCFG().blockIterator();
        while (bbIter.hasNext()) {
            BasicBlock basicBlock = bbIter.next();

            if (basicBlock.isNullCheck()) {
                analyzeNullCheck(invDataflow, basicBlock);
            } else if (!basicBlock.isEmpty()) {
                // Look for all reference comparisons where
                // - both values compared are definitely null, or
                // - one value is definitely null and one is definitely not null
                // These cases are not null dereferences,
                // but they are quite likely to indicate an error, so while
                // we've got
                // information about null values, we may as well report them.
                InstructionHandle lastHandle = basicBlock.getLastInstruction();
                Instruction last = lastHandle.getInstruction();
                switch (last.getOpcode()) {
                case Constants.IF_ACMPEQ:
                case Constants.IF_ACMPNE:
                    analyzeRefComparisonBranch(basicBlock, lastHandle);
                    break;
                case Constants.IFNULL:
                case Constants.IFNONNULL:
                    analyzeIfNullBranch(basicBlock, lastHandle);
                    break;
                }
            }
        }
    }

    /**
     * Examine null values. Report any that are guaranteed to be dereferenced on
     * non-implicit-exception paths.
     *
     * @throws CFGBuilderException
     * @throws DataflowAnalysisException
     */
    private void examineNullValues() throws CFGBuilderException, DataflowAnalysisException {
        Set<LocationWhereValueBecomesNull> locationWhereValueBecomesNullSet = invDataflow.getAnalysis()
                .getLocationWhereValueBecomesNullSet();
        if (DEBUG_DEREFS) {
            System.out.println("----------------------- examineNullValues " + locationWhereValueBecomesNullSet.size());
        }

        Map<ValueNumber, SortedSet<Location>> bugStatementLocationMap = new HashMap<ValueNumber, SortedSet<Location>>();
        // Inspect the method for locations where a null value is guaranteed to
        // be dereferenced. Add the dereference locations
        Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap = new HashMap<ValueNumber, NullValueUnconditionalDeref>();

        // Check every location
        CFG cfg = classContext.getCFG(method);
        for (Iterator<Location> i = cfg.locationIterator(); i.hasNext();) {
            Location location = i.next();

            if (DEBUG_DEREFS) {
                System.out.println("At location " + location);
            }

            checkForUnconditionallyDereferencedNullValues(location, bugStatementLocationMap, nullValueGuaranteedDerefMap,
                    vnaDataflow.getFactAtLocation(location), invDataflow.getFactAtLocation(location),
                    uvdDataflow.getFactAfterLocation(location), false);
        }
        HashSet<ValueNumber> npeIfStatementCovered = new HashSet<ValueNumber>(nullValueGuaranteedDerefMap.keySet());
        Map<ValueNumber, SortedSet<Location>> bugEdgeLocationMap = new HashMap<ValueNumber, SortedSet<Location>>();

        checkEdges(cfg, nullValueGuaranteedDerefMap, bugEdgeLocationMap);
        
        Map<ValueNumber, SortedSet<Location>> bugLocationMap = bugEdgeLocationMap;
        bugLocationMap.putAll(bugStatementLocationMap);
        
        // For each value number that is null somewhere in the
        // method, collect the set of locations where it becomes null.
        // FIXME: we may see some locations that are not guaranteed to be
        // dereferenced (how to fix this?)
        Map<ValueNumber, Set<Location>> nullValueAssignmentMap = findNullAssignments(locationWhereValueBecomesNullSet);

        reportBugs(nullValueGuaranteedDerefMap, npeIfStatementCovered, bugLocationMap, nullValueAssignmentMap);
    }

    /**
     * @param locationWhereValueBecomesNullSet
     * @return
     */
    public Map<ValueNumber, Set<Location>> findNullAssignments(Set<LocationWhereValueBecomesNull> locationWhereValueBecomesNullSet) {
        Map<ValueNumber, Set<Location>> nullValueAssignmentMap = new HashMap<ValueNumber, Set<Location>>();
        for (LocationWhereValueBecomesNull lwvbn : locationWhereValueBecomesNullSet) {
            if (DEBUG_DEREFS)
                System.out.println("OOO " + lwvbn);
            Set<Location> locationSet = nullValueAssignmentMap.get(lwvbn.getValueNumber());
            if (locationSet == null) {
                locationSet = new HashSet<Location>(4);
                nullValueAssignmentMap.put(lwvbn.getValueNumber(), locationSet);
            }
            locationSet.add(lwvbn.getLocation());
            if (DEBUG_DEREFS)
                System.out.println(lwvbn.getValueNumber() + " becomes null at " + lwvbn.getLocation());
        }
        return nullValueAssignmentMap;
    }

    /**
     * @param nullValueGuaranteedDerefMap
     * @param npeIfStatementCovered
     * @param bugLocationMap
     * @param nullValueAssignmentMap
     * @throws CFGBuilderException
     * @throws DataflowAnalysisException
     */
    public void reportBugs(Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap,
            HashSet<ValueNumber> npeIfStatementCovered, Map<ValueNumber, SortedSet<Location>> bugLocationMap,
            Map<ValueNumber, Set<Location>> nullValueAssignmentMap) throws CFGBuilderException, DataflowAnalysisException {
        // Report
        for (Map.Entry<ValueNumber, NullValueUnconditionalDeref> e : nullValueGuaranteedDerefMap.entrySet()) {
            ValueNumber valueNumber = e.getKey();
            Set<Location> derefLocationSet = e.getValue().getDerefLocationSet();
            Set<Location> assignedNullLocationSet = nullValueAssignmentMap.get(valueNumber);
            if (assignedNullLocationSet == null) {
                if (DEBUG_DEREFS) {
                    String where = classContext.getJavaClass().getClassName() + "." + method.getName() + ":"
                            + method.getSignature();
                    System.out.println("Problem at " + where);
                    System.out.println("Value number " + valueNumber);
                    for (Location loc : derefLocationSet) {
                        System.out.println("Dereference at " + loc);
                    }
                }
                // TODO: figure out why this is failing
                if (false)
                    assert false : "No assigned NullLocationSet for " + valueNumber + " in " + nullValueAssignmentMap.keySet()
                            + " while analyzing " + classContext.getJavaClass().getClassName() + "." + method.getName();
                assignedNullLocationSet = Collections.<Location> emptySet();
            }
            SortedSet<Location> knownNullAndDoomedAt = bugLocationMap.get(valueNumber);

            BugAnnotation variableAnnotation = null;
            try {
                for (Location loc : derefLocationSet) {
                    variableAnnotation = ValueNumberSourceInfo.findAnnotationFromValueNumber(method, loc, valueNumber,
                            vnaDataflow.getFactAtLocation(loc), "VALUE_OF");
                    if (variableAnnotation != null)
                        break;
                }
                if (variableAnnotation == null)
                    for (Location loc : knownNullAndDoomedAt) {
                        variableAnnotation = ValueNumberSourceInfo.findAnnotationFromValueNumber(method, loc, valueNumber,
                                vnaDataflow.getFactAtLocation(loc), "VALUE_OF");
                        if (variableAnnotation != null)
                            break;
                    }
                if (variableAnnotation == null)
                    for (Location loc : assignedNullLocationSet) {
                        variableAnnotation = ValueNumberSourceInfo.findAnnotationFromValueNumber(method, loc, valueNumber,
                                vnaDataflow.getFactAtLocation(loc), "VALUE_OF");
                        if (variableAnnotation != null)
                            break;
                    }

            } catch (DataflowAnalysisException e2) {
            }
            if (variableAnnotation == null)
                variableAnnotation = new LocalVariableAnnotation("?", -1, derefLocationSet.iterator().next().getHandle()
                        .getPosition());

            if (PRUNE_GUARANTEED_DEREFERENCES) {
                PostDominatorsAnalysis postDomAnalysis = classContext.getNonExceptionPostDominatorsAnalysis(method);

                removeStrictlyPostDominatedLocations(derefLocationSet, postDomAnalysis);

                removeStrictlyPostDominatedLocations(assignedNullLocationSet, postDomAnalysis);
                DominatorsAnalysis domAnalysis = classContext.getNonExceptionDominatorsAnalysis(method);
                removeStrictlyDominatedLocations(knownNullAndDoomedAt, domAnalysis);

            }

            collector.foundGuaranteedNullDeref(assignedNullLocationSet, derefLocationSet, knownNullAndDoomedAt, vnaDataflow,
                    valueNumber, variableAnnotation, e.getValue(), npeIfStatementCovered.contains(valueNumber));
        }
    }

    /**
     * @param cfg
     * @param nullValueGuaranteedDerefMap
     * @param bugEdgeLocationMap
     * @throws DataflowAnalysisException
     */
    public void checkEdges(CFG cfg, Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap,
            Map<ValueNumber, SortedSet<Location>> bugEdgeLocationMap) throws DataflowAnalysisException {
        // Check every non-exception control edge
        for (Iterator<Edge> i = cfg.edgeIterator(); i.hasNext();) {
            Edge edge = i.next();

            UnconditionalValueDerefSet uvdFact = uvdDataflow.getFactOnEdge(edge);

            if (uvdFact.isEmpty())
                continue;
            
            if (edge.isExceptionEdge()) {
                if (DEBUG_DEREFS) {
                    System.out.println("On exception edge " + edge.formatAsString(false));
                }

                // continue;
            }

            if (DEBUG_DEREFS) {
                System.out.println("On edge " + edge.formatAsString(false));
            }

            BasicBlock source = edge.getSource();
            ValueNumberFrame vnaFact = vnaDataflow.getResultFact(source);
            IsNullValueFrame invFact = invDataflow.getFactAtMidEdge(edge);

            Location location = null;
            if (edge.isExceptionEdge()) {
                BasicBlock b = cfg.getSuccessorWithEdgeType(source, EdgeTypes.FALL_THROUGH_EDGE);
                if (b != null)
                    location = new Location(source.getExceptionThrower(), b);
            } else
                location = Location.getLastLocation(source);

            if (location != null) {
                Instruction in = location.getHandle().getInstruction();
                if (assertionMethods.isAssertionInstruction(in, classContext.getConstantPoolGen())) {
                    if (DEBUG_DEREFS)
                        System.out.println("Skipping because it is an assertion method ");
                    continue;
                }

                checkForUnconditionallyDereferencedNullValues(location, bugEdgeLocationMap, nullValueGuaranteedDerefMap, vnaFact,
                        invFact, uvdFact, true);
            }
        }
    }

    private void removeStrictlyPostDominatedLocations(Set<Location> locations, PostDominatorsAnalysis postDomAnalysis) {
        BitSet strictlyDominated = new BitSet();
        for (Location loc : locations) {
            BitSet allDominatedBy = postDomAnalysis.getAllDominatedBy(loc.getBasicBlock());
            allDominatedBy.clear(loc.getBasicBlock().getLabel());
            strictlyDominated.or(allDominatedBy);
        }
        LinkedList<Location> locations2 = new LinkedList<Location>(locations);

        for (Iterator<Location> i = locations.iterator(); i.hasNext();) {
            Location loc = i.next();
            if (strictlyDominated.get(loc.getBasicBlock().getLabel())) {
                i.remove();
                continue;
            }
            for (Location loc2 : locations2) {
                if (loc.getBasicBlock().equals(loc2.getBasicBlock())
                        && loc.getHandle().getPosition() > loc2.getHandle().getPosition()) {
                    i.remove();
                    break;
                }
            }
        }
    }

    private void removeStrictlyDominatedLocations(Set<Location> locations, DominatorsAnalysis domAnalysis) {
        BitSet strictlyDominated = new BitSet();
        for (Location loc : locations) {
            BitSet allDominatedBy = domAnalysis.getAllDominatedBy(loc.getBasicBlock());
            allDominatedBy.clear(loc.getBasicBlock().getLabel());
            strictlyDominated.or(allDominatedBy);
        }
        LinkedList<Location> locations2 = new LinkedList<Location>(locations);

        for (Iterator<Location> i = locations.iterator(); i.hasNext();) {
            Location loc = i.next();
            if (strictlyDominated.get(loc.getBasicBlock().getLabel())) {
                i.remove();
                continue;
            }
            for (Location loc2 : locations2) {
                if (loc.getBasicBlock().equals(loc2.getBasicBlock())
                        && loc.getHandle().getPosition() > loc2.getHandle().getPosition()) {
                    i.remove();
                    break;
                }
            }
        }
    }

    private static final boolean MY_DEBUG = false;

    /**
     * Check for unconditionally dereferenced null values at a particular
     * location in the CFG.
     *
     * @param thisLocation
     *            TODO
     * @param knownNullAndDoomedAt
     *            TODO
     * @param nullValueGuaranteedDerefMap
     *            map to be populated with null values and where they are
     *            derefed
     * @param vnaFrame
     *            value number frame to check
     * @param invFrame
     *            null-value frame to check
     * @param derefSet
     *            set of unconditionally derefed values at this location
     * @param isEdge
     *            TODO
     */
    private void checkForUnconditionallyDereferencedNullValues(Location thisLocation,
            Map<ValueNumber, SortedSet<Location>> knownNullAndDoomedAt,
            Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap, ValueNumberFrame vnaFrame,
            IsNullValueFrame invFrame, UnconditionalValueDerefSet derefSet, boolean isEdge) {

        if (DEBUG_DEREFS) {
            System.out.println("vna *** " + vnaFrame);
            System.out.println("inv *** " + invFrame);
            System.out.println("deref * " + derefSet);
        }

        // Make sure the frames contain meaningful information
        if (!vnaFrame.isValid() || !invFrame.isValid()  || vnaFrame.getNumLocals() != invFrame.getNumLocals()
                || derefSet.isEmpty()) {
            return;
        }

        int slots;
        if (vnaFrame.getNumSlots() == invFrame.getNumSlots())
            slots = vnaFrame.getNumSlots();
        else
            slots = vnaFrame.getNumLocals();
        
        if (false) {
            InstructionHandle handle = thisLocation.getHandle();
            if (handle != null && handle.getInstruction() instanceof NullnessConversationInstruction) {
                try {
                    IsNullValue isNullValue = invFrame.getStackValue(0);
                    ValueNumber valueNumber = vnaFrame.getStackValue(0);
                    if (!isNullValue.isDefinitelyNotNull() && !isNullValue.isDefinitelyNull() && derefSet.isUnconditionallyDereferenced(valueNumber)) {
                        Location where = thisLocation;
                        SortedSet<Location> doomedAt = knownNullAndDoomedAt.get(valueNumber);
                        if (doomedAt == null)
                            knownNullAndDoomedAt.put(valueNumber, doomedAt = new TreeSet<Location>());
                        doomedAt.add(thisLocation);
                        NullValueUnconditionalDeref nullValueUnconditionalDeref = nullValueGuaranteedDerefMap.get(valueNumber);
                        if (nullValueUnconditionalDeref != null) {
                            Set<Location> derefLocationSet = nullValueUnconditionalDeref.getDerefLocationSet();
                            if (derefLocationSet.size() == 1)
                                where = derefLocationSet.iterator().next();
                        }
                        noteUnconditionallyDereferencedNullValue(where, knownNullAndDoomedAt, nullValueGuaranteedDerefMap, derefSet,
                                isNullValue, valueNumber);
                    }
                } catch (DataflowAnalysisException e) {
                    AnalysisContext.logError("huh", e);
                }
            }

        }

        // See if there are any definitely-null values in the frame
        for (int j = 0; j < slots; j++) {
            IsNullValue isNullValue = invFrame.getValue(j);
            ValueNumber valueNumber = vnaFrame.getValue(j);
            if ((isNullValue.isDefinitelyNull() || isNullValue.isNullOnSomePath() && isNullValue.isReturnValue())
                    && (derefSet.isUnconditionallyDereferenced(valueNumber))) {
                if (MY_DEBUG) {
                    System.out.println("Found NP bug");
                    System.out.println("Location: " + thisLocation);
                    System.out.println("Value number frame: " + vnaFrame);
                    System.out.println("Value number: " + valueNumber);
                    System.out.println("IsNullValue frame: " + invFrame);
                    System.out.println("IsNullValue value: " + isNullValue);
                    System.out.println("Unconditional dere framef: " + derefSet);
                    System.out.println("Unconditionally dereferenced: " + derefSet.isUnconditionallyDereferenced(valueNumber));

                }
                Location where = thisLocation;
                if (!isEdge && isNullValue.isNullOnSomePath() && isNullValue.isReturnValue()) {
                    try {
                        where = classContext.getCFG(method).getPreviousLocation(where);
                    } catch (CFGBuilderException e) {
                        AnalysisContext.logError(
                                "Error looking for previous instruction to " + where + " in "
                                        + classContext.getFullyQualifiedMethodName(method), e);
                    }
                }
                noteUnconditionallyDereferencedNullValue(where, knownNullAndDoomedAt, nullValueGuaranteedDerefMap, derefSet,
                        isNullValue, valueNumber);
            }
        }

        // See if there are any known-null values in the heap that
        // will be dereferenced in the future.
        for (Map.Entry<ValueNumber, IsNullValue> entry : invFrame.getKnownValueMapEntrySet()) {
            ValueNumber valueNumber = entry.getKey();
            IsNullValue isNullValue = entry.getValue();
            if ((isNullValue.isDefinitelyNull() || isNullValue.isNullOnSomePath()
                    && (isNullValue.isReturnValue() || isNullValue.isFieldValue()))
                    && derefSet.isUnconditionallyDereferenced(valueNumber)) {

                noteUnconditionallyDereferencedNullValue(thisLocation, knownNullAndDoomedAt, nullValueGuaranteedDerefMap,
                        derefSet, isNullValue, valueNumber);
            }
        }
    }

    /**
     * Note the locations where a known-null value is unconditionally
     * dereferenced.
     *
     * @param thisLocation
     *            TODO
     * @param bugLocations
     *            TODO
     * @param nullValueGuaranteedDerefMap
     *            map of null values to sets of Locations where they are derefed
     * @param derefSet
     *            set of values known to be unconditionally dereferenced
     * @param isNullValue
     *            the null value
     * @param valueNumber
     *            the value number of the null value
     */
    private void noteUnconditionallyDereferencedNullValue(Location thisLocation,
            Map<ValueNumber, SortedSet<Location>> bugLocations,
            Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap, UnconditionalValueDerefSet derefSet,
            IsNullValue isNullValue, ValueNumber valueNumber) {
        if (DEBUG) {
            System.out.println("%%% HIT for value number " + valueNumber + " @ " + thisLocation);
        }
        Set<Location> unconditionalDerefLocationSet = derefSet.getUnconditionalDerefLocationSet(valueNumber);
        if (unconditionalDerefLocationSet.isEmpty()) {
            AnalysisContext.logError("empty set of unconditionally dereferenced locations at "
                    + thisLocation.getHandle().getPosition() + " in " + classContext.getClassDescriptor() + "."
                    + method.getName() + method.getSignature());
            return;
        }

        // OK, we have a null value that is unconditionally
        // derferenced. Make a note of the locations where it
        // will be dereferenced.
        NullValueUnconditionalDeref thisNullValueDeref = nullValueGuaranteedDerefMap.get(valueNumber);
        if (thisNullValueDeref == null) {
            thisNullValueDeref = new NullValueUnconditionalDeref();
            nullValueGuaranteedDerefMap.put(valueNumber, thisNullValueDeref);
        }
        thisNullValueDeref.add(isNullValue, unconditionalDerefLocationSet);

        if (thisLocation != null) {
            SortedSet<Location> locationsForThisBug = bugLocations.get(valueNumber);

            if (locationsForThisBug == null) {
                locationsForThisBug = new TreeSet<Location>();
                bugLocations.put(valueNumber, locationsForThisBug);
            }
            locationsForThisBug.add(thisLocation);
        }
    }

    /**
     * Examine redundant branches.
     */
    private void examineRedundantBranches() {
        for (RedundantBranch redundantBranch : redundantBranchList) {
            if (DEBUG)
                System.out.println("Redundant branch: " + redundantBranch);
            int lineNumber = redundantBranch.lineNumber;

            // The source to bytecode compiler may sometimes duplicate blocks of
            // code along different control paths. So, to report the bug,
            // we check to ensure that the branch is REALLY determined each
            // place it is duplicated, and that it is determined in the same
            // way.

            boolean confused = undeterminedBranchSet.get(lineNumber)
                    || (definitelySameBranchSet.get(lineNumber) && definitelyDifferentBranchSet.get(lineNumber));

            // confused if there is JSR confusion or multiple null checks with
            // different results on the same line

            boolean reportIt = true;

            if (lineMentionedMultipleTimes.get(lineNumber) && confused)
                reportIt = false;
            else if (redundantBranch.location.getBasicBlock().isInJSRSubroutine() /*
                                                                                   * occurs
                                                                                   * in
                                                                                   * a
                                                                                   * JSR
                                                                                   */
                    && confused)
                reportIt = false;
            else {
                int pc = redundantBranch.location.getHandle().getPosition();
                for (CodeException e : method.getCode().getExceptionTable()) {
                    if (e.getCatchType() == 0 && e.getStartPC() != e.getHandlerPC() && e.getEndPC() <= pc
                            && pc <= e.getEndPC() + 5)
                        reportIt = false;
                }
            }

            if (reportIt) {
                collector.foundRedundantNullCheck(redundantBranch.location, redundantBranch);
            }
        }
    }

    private void analyzeRefComparisonBranch(BasicBlock basicBlock, InstructionHandle lastHandle) throws DataflowAnalysisException {

        Location location = new Location(lastHandle, basicBlock);

        IsNullValueFrame frame = invDataflow.getFactAtLocation(location);
        if (!frame.isValid()) {
            // Probably dead code due to pruning infeasible exception edges.
            return;
        }
        if (frame.getStackDepth() < 2)
            throw new DataflowAnalysisException("Stack underflow at " + lastHandle);

        // Find the line number.
        int lineNumber = getLineNumber(method, lastHandle);
        if (lineNumber < 0)
            return;

        int numSlots = frame.getNumSlots();
        IsNullValue top = frame.getValue(numSlots - 1);
        IsNullValue topNext = frame.getValue(numSlots - 2);

        boolean definitelySame = top.isDefinitelyNull() && topNext.isDefinitelyNull();
        boolean definitelyDifferent = (top.isDefinitelyNull() && topNext.isDefinitelyNotNull())
                || (top.isDefinitelyNotNull() && topNext.isDefinitelyNull());

        if (definitelySame || definitelyDifferent) {
            if (definitelySame) {
                if (DEBUG)
                    System.out.println("Line " + lineNumber + " always same");
                definitelySameBranchSet.set(lineNumber);
            }
            if (definitelyDifferent) {
                if (DEBUG)
                    System.out.println("Line " + lineNumber + " always different");
                definitelyDifferentBranchSet.set(lineNumber);
            }

            RedundantBranch redundantBranch = new RedundantBranch(location, lineNumber, top, topNext);

            // Figure out which control edge is made infeasible by the redundant
            // comparison
            boolean wantSame = (lastHandle.getInstruction().getOpcode() == Constants.IF_ACMPEQ);
            int infeasibleEdgeType = (wantSame == definitelySame) ? EdgeTypes.FALL_THROUGH_EDGE : EdgeTypes.IFCMP_EDGE;
            Edge infeasibleEdge = invDataflow.getCFG().getOutgoingEdgeWithType(basicBlock, infeasibleEdgeType);
            redundantBranch.setInfeasibleEdge(infeasibleEdge);

            if (DEBUG)
                System.out.println("Adding redundant branch: " + redundantBranch);
            redundantBranchList.add(redundantBranch);
        } else {
            if (DEBUG)
                System.out.println("Line " + lineNumber + " undetermined");
            undeterminedBranchSet.set(lineNumber);
        }
    }

    // This is called for both IFNULL and IFNONNULL instructions.
    private void analyzeIfNullBranch(BasicBlock basicBlock, InstructionHandle lastHandle) throws DataflowAnalysisException {

        Location location = new Location(lastHandle, basicBlock);

        IsNullValueFrame frame = invDataflow.getFactAtLocation(location);

        if (!frame.isValid()) {
            // This is probably dead code due to an infeasible exception edge.
            return;
        }

        IsNullValue top = frame.getTopValue();

        // Find the line number.
        int lineNumber = getLineNumber(method, lastHandle);
        if (lineNumber < 0)
            return;

        if (!(top.isDefinitelyNull() || top.isDefinitelyNotNull())) {
            if (DEBUG)
                System.out.println("Line " + lineNumber + " undetermined");
            undeterminedBranchSet.set(lineNumber);
            return;
        }

        // Figure out if the branch is always taken
        // or always not taken.
        short opcode = lastHandle.getInstruction().getOpcode();
        boolean definitelySame = top.isDefinitelyNull();
        if (opcode != Constants.IFNULL)
            definitelySame = !definitelySame;

        if (definitelySame) {
            if (DEBUG)
                System.out.println("Line " + lineNumber + " always same");
            definitelySameBranchSet.set(lineNumber);
        } else {
            if (DEBUG)
                System.out.println("Line " + lineNumber + " always different");
            definitelyDifferentBranchSet.set(lineNumber);
        }

        RedundantBranch redundantBranch = new RedundantBranch(location, lineNumber, top);

        // Determine which control edge is made infeasible by the redundant
        // comparison
        boolean wantNull = (opcode == Constants.IFNULL);
        int infeasibleEdgeType = (wantNull == top.isDefinitelyNull()) ? EdgeTypes.FALL_THROUGH_EDGE : EdgeTypes.IFCMP_EDGE;
        Edge infeasibleEdge = invDataflow.getCFG().getOutgoingEdgeWithType(basicBlock, infeasibleEdgeType);
        redundantBranch.setInfeasibleEdge(infeasibleEdge);

        if (DEBUG)
            System.out.println("Adding redundant branch: " + redundantBranch);
        redundantBranchList.add(redundantBranch);
    }

    private void analyzeNullCheck(IsNullValueDataflow invDataflow, BasicBlock basicBlock) throws DataflowAnalysisException,
            CFGBuilderException {
        // Look for null checks where the value checked is definitely
        // null or null on some path.

        InstructionHandle exceptionThrowerHandle = basicBlock.getExceptionThrower();
        Instruction exceptionThrower = exceptionThrowerHandle.getInstruction();

        // Get the stack values at entry to the null check.
        IsNullValueFrame frame = invDataflow.getStartFact(basicBlock);
        if (!frame.isValid())
            return;

        // Could the reference be null?
        IsNullValue refValue = frame.getInstance(exceptionThrower, classContext.getConstantPoolGen());
        if (DEBUG) {
            System.out.println("For basic block " + basicBlock + " value is " + refValue);
        }
        if (refValue.isDefinitelyNotNull())
            return;
        if (false && !refValue.mightBeNull())
            return;

        if (!refValue.isDefinitelyNull())
            return;
        // Get the value number
        ValueNumberFrame vnaFrame = classContext.getValueNumberDataflow(method).getStartFact(basicBlock);
        if (!vnaFrame.isValid())
            return;
        ValueNumber valueNumber = vnaFrame.getInstance(exceptionThrower, classContext.getConstantPoolGen());
        Location location = new Location(exceptionThrowerHandle, basicBlock);
        if (DEBUG)
            System.out.println("Warning: VN " + valueNumber + " invf: " + frame + " @ " + location);

        boolean isConsistent = true;
        Iterator<BasicBlock> bbIter = invDataflow.getCFG().blockIterator();
        LineNumberTable table = method.getLineNumberTable();
        int position = exceptionThrowerHandle.getPosition();
        int line = table == null ? 0 : table.getSourceLine(position);
        while (bbIter.hasNext()) {
            BasicBlock bb = bbIter.next();

            if (!bb.isNullCheck())
                continue;

            InstructionHandle eth = bb.getExceptionThrower();
            if (eth == exceptionThrowerHandle)
                continue;
            if (eth.getInstruction().getOpcode() != exceptionThrower.getOpcode())
                continue;

            int ePosition = eth.getPosition();
            if (ePosition == position || table != null && line == table.getSourceLine(ePosition)) {

                IsNullValueFrame frame2 = invDataflow.getStartFact(bb);
                if (!frame2.isValid())
                    continue;
                // apparent clone
                IsNullValue rv = frame2.getInstance(eth.getInstruction(), classContext.getConstantPoolGen());
                if (!rv.equals(refValue))
                    isConsistent = false;
            }

        }
        // Issue a warning
        collector.foundNullDeref(location, valueNumber, refValue, vnaFrame, isConsistent);
    }

    /**
     * @deprecated Use
     *             {@link ValueNumberSourceInfo#findXFieldFromValueNumber(Method,Location,ValueNumber,ValueNumberFrame)}
     *             instead
     */
    @Deprecated
    public static XField findXFieldFromValueNumber(Method method, Location location, ValueNumber valueNumber,
            ValueNumberFrame vnaFrame) {
        return ValueNumberSourceInfo.findXFieldFromValueNumber(method, location, valueNumber, vnaFrame);
    }

    /**
     * @deprecated Use
     *             {@link ValueNumberSourceInfo#findFieldAnnotationFromValueNumber(Method,Location,ValueNumber,ValueNumberFrame)}
     *             instead
     */
    @Deprecated
    public static FieldAnnotation findFieldAnnotationFromValueNumber(Method method, Location location, ValueNumber valueNumber,
            ValueNumberFrame vnaFrame) {
        return ValueNumberSourceInfo.findFieldAnnotationFromValueNumber(method, location, valueNumber, vnaFrame);
    }

    /**
     * @deprecated Use
     *             {@link ValueNumberSourceInfo#findLocalAnnotationFromValueNumber(Method,Location,ValueNumber,ValueNumberFrame)}
     *             instead
     */
    @Deprecated
    public static LocalVariableAnnotation findLocalAnnotationFromValueNumber(Method method, Location location,
            ValueNumber valueNumber, ValueNumberFrame vnaFrame) {
        return ValueNumberSourceInfo.findLocalAnnotationFromValueNumber(method, location, valueNumber, vnaFrame);
    }

    /**
     * @param method
     *            TODO
     * @param location
     * @param valueNumber
     * @param vnaFrame
     * @return the annotation
     * @deprecated Use
     *             {@link ValueNumberSourceInfo#findRequiredAnnotationFromValueNumber(Method,Location,ValueNumber,ValueNumberFrame, String)}
     *             instead
     */
    @Deprecated
    public static BugAnnotation findAnnotationFromValueNumber(Method method, Location location, ValueNumber valueNumber,
            ValueNumberFrame vnaFrame) {
        return ValueNumberSourceInfo.findRequiredAnnotationFromValueNumber(method, location, valueNumber, vnaFrame, null);
    }

    private static int getLineNumber(Method method, InstructionHandle handle) {
        LineNumberTable table = method.getCode().getLineNumberTable();
        if (table == null)
            return -1;
        return table.getSourceLine(handle.getPosition());
    }
}
