/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2006, 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.Iterator;
import java.util.Set;

import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ARETURN;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldInstruction;
import org.apache.bcel.generic.IFNONNULL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.PUTFIELD;
import org.apache.bcel.generic.PUTSTATIC;

import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisContext;
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.Hierarchy;
import edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase;
import edu.umd.cs.findbugs.ba.JavaClassAndMethod;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.NullnessAnnotation;
import edu.umd.cs.findbugs.ba.SignatureParser;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefAnalysis;
import edu.umd.cs.findbugs.ba.interproc.ParameterProperty;
import edu.umd.cs.findbugs.ba.type.TypeDataflow;
import edu.umd.cs.findbugs.ba.type.TypeFrame;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;

/**
 * @author pugh
 */
public class DerefFinder {

    public static boolean DEBUG = SystemProperties.getBoolean("deref.finder.debug");

    public static UsagesRequiringNonNullValues getAnalysis(ClassContext classContext, Method method) {
        XMethod thisMethod = classContext.getXClass().findMethod(method.getName(), method.getSignature(), method.isStatic());
        if (DEBUG) {
            System.out.println(thisMethod);
        }
        UsagesRequiringNonNullValues derefs = new UsagesRequiringNonNullValues();
        try {

            CFG cfg = classContext.getCFG(method);

            ValueNumberDataflow vna = classContext.getValueNumberDataflow(method);
            TypeDataflow typeDataflow = classContext.getTypeDataflow(method);
            INullnessAnnotationDatabase db = AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase();

            ParameterNullnessPropertyDatabase unconditionalDerefParamDatabase = AnalysisContext.currentAnalysisContext()
                    .getUnconditionalDerefParamDatabase();
            Iterator<BasicBlock> bbIter = cfg.blockIterator();
            ConstantPoolGen cpg = classContext.getConstantPoolGen();
            ValueNumber valueNumberForThis = null;
            if (!method.isStatic()) {
                ValueNumberFrame frameAtEntry = vna.getStartFact(cfg.getEntry());
                valueNumberForThis = frameAtEntry.getValue(0);
            }

            NullnessAnnotation methodAnnotation = getMethodNullnessAnnotation(classContext, method);

            while (bbIter.hasNext()) {
                BasicBlock basicBlock = bbIter.next();

                if (basicBlock.isNullCheck()) {
                    InstructionHandle exceptionThrowerHandle = basicBlock.getExceptionThrower();
                    Instruction exceptionThrower = exceptionThrowerHandle.getInstruction();
                    ValueNumberFrame vnaFrame = vna.getStartFact(basicBlock);
                    if (!vnaFrame.isValid()) {
                        continue;
                    }
                    ValueNumber valueNumber = vnaFrame.getInstance(exceptionThrower, cpg);

                    Location location = new Location(exceptionThrowerHandle, basicBlock);
                    if (valueNumberForThis != valueNumber) {
                        derefs.add(location, valueNumber, PointerUsageRequiringNonNullValue.getPointerDereference());
                    }

                }
            }

            for (Iterator<Location> i = cfg.locationIterator(); i.hasNext();) {
                Location location = i.next();
                InstructionHandle handle = location.getHandle();
                Instruction ins = handle.getInstruction();
                ValueNumberFrame valueNumberFrame = vna.getFactAtLocation(location);
                TypeFrame typeFrame = typeDataflow.getFactAtLocation(location);
                if (ins instanceof InvokeInstruction) {
                    InvokeInstruction inv = (InvokeInstruction) ins;
                    XMethod m = XFactory.createXMethod(inv, cpg);
                    SignatureParser sigParser = new SignatureParser(m.getSignature());
                    int numParams = sigParser.getNumParameters();

                    // Check nonnull annotations

                    for (int j = 0; j < numParams; j++) {
                        if (db.parameterMustBeNonNull(m, j)) {
                            int slot = sigParser.getSlotsFromTopOfStackForParameter(j);
                            ValueNumber valueNumber = valueNumberFrame.getStackValue(slot);
                            if (valueNumberForThis != valueNumber) {
                                derefs.add(location, valueNumber,
                                        PointerUsageRequiringNonNullValue.getPassedAsNonNullParameter(m, j));
                            }
                        }
                    }

                    // Check actual targets
                    try {
                        Set<JavaClassAndMethod> targetMethodSet = Hierarchy.resolveMethodCallTargets(inv, typeFrame, cpg);
                        BitSet unconditionallyDereferencedNullArgSet = null;
                        for (JavaClassAndMethod targetMethod : targetMethodSet) {

                            ParameterProperty property = unconditionalDerefParamDatabase.getProperty(targetMethod
                                    .toMethodDescriptor());
                            if (property == null) {
                                unconditionallyDereferencedNullArgSet = null;
                                break;
                            }
                            BitSet foo = property.getAsBitSet();
                            if (unconditionallyDereferencedNullArgSet == null) {
                                unconditionallyDereferencedNullArgSet = foo;
                            } else {
                                unconditionallyDereferencedNullArgSet.and(foo);
                            }
                            if (unconditionallyDereferencedNullArgSet.isEmpty()) {
                                break;
                            }
                        }

                        if (unconditionallyDereferencedNullArgSet != null && !unconditionallyDereferencedNullArgSet.isEmpty()
                                && valueNumberFrame.isValid()) {
                            for (int j = unconditionallyDereferencedNullArgSet.nextSetBit(0); j >= 0; j = unconditionallyDereferencedNullArgSet
                                    .nextSetBit(j + 1)) {
                                int slot = sigParser.getSlotsFromTopOfStackForParameter(j);
                                ValueNumber valueNumber = valueNumberFrame.getStackValue(slot);
                                if (valueNumberForThis != valueNumber) {
                                    derefs.add(location, valueNumber,
                                            PointerUsageRequiringNonNullValue.getPassedAsNonNullParameter(m, j));
                                }
                            }
                        }

                    } catch (ClassNotFoundException e) {
                        AnalysisContext.reportMissingClass(e);
                    }

                } else if (ins instanceof IFNONNULL && UnconditionalValueDerefAnalysis.isNullCheck(handle, cpg)) {
                    ValueNumber valueNumber = valueNumberFrame.getTopValue();
                    derefs.add(location, valueNumber, PointerUsageRequiringNonNullValue.getPointerNullChecked());
                } else if (ins instanceof ARETURN && methodAnnotation == NullnessAnnotation.NONNULL) {
                    ValueNumber valueNumber = valueNumberFrame.getTopValue();
                    if (valueNumberForThis != valueNumber) {
                        derefs.add(location, valueNumber,
                                PointerUsageRequiringNonNullValue.getReturnFromNonNullMethod(thisMethod));
                    }

                } else if (ins instanceof PUTFIELD || ins instanceof PUTSTATIC) {
                    FieldInstruction inf = (FieldInstruction) ins;
                    XField field = XFactory.createXField(inf, cpg);
                    NullnessAnnotation annotation = AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase()
                            .getResolvedAnnotation(field, false);
                    if (annotation == NullnessAnnotation.NONNULL) {
                        ValueNumber valueNumber = valueNumberFrame.getTopValue();
                        if (valueNumberForThis != valueNumber) {
                            derefs.add(location, valueNumber, PointerUsageRequiringNonNullValue.getStoredIntoNonNullField(field));
                        }
                    }

                }
            }

        } catch (CFGBuilderException e) {
            AnalysisContext.logError("Error generating derefs for " + thisMethod, e);
        } catch (DataflowAnalysisException e) {
            AnalysisContext.logError("Error generating derefs for " + thisMethod, e);
        }
        return derefs;
    }

    public static NullnessAnnotation getMethodNullnessAnnotation(ClassContext classContext, Method method) {

        if (method.getSignature().indexOf(")L") >= 0 || method.getSignature().indexOf(")[") >= 0) {

            XMethod m = XFactory.createXMethod(classContext.getJavaClass(), method);
            return AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase().getResolvedAnnotation(m, false);
        }
        return NullnessAnnotation.UNKNOWN_NULLNESS;
    }

}
