/*
 * FindBugs - Find bugs in Java programs
 * 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.detect;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.ConstantValue;
import org.apache.bcel.classfile.ElementValue;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.Signature;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;

import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.DeepSubtypeAnalysis;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.Priorities;
import edu.umd.cs.findbugs.ProgramPoint;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.ba.generic.GenericObjectType;
import edu.umd.cs.findbugs.ba.generic.GenericUtilities;
import edu.umd.cs.findbugs.ba.npe.IsNullValue;
import edu.umd.cs.findbugs.ba.npe.IsNullValueDataflow;
import edu.umd.cs.findbugs.ba.npe.IsNullValueFrame;
import edu.umd.cs.findbugs.ba.vna.AvailableLoad;
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.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
import edu.umd.cs.findbugs.util.Bag;
import edu.umd.cs.findbugs.util.ClassName;
import edu.umd.cs.findbugs.util.Util;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;

public class UnreadFields extends OpcodeStackDetector {
    private static final boolean DEBUG = SystemProperties.getBoolean("unreadfields.debug");

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#isContainerField(XField)} instead
     */
    @Deprecated
    public boolean isContainerField(XField f) {
        return data.isContainerField(f);
    }



    boolean hasNativeMethods;

    boolean isSerializable;

    boolean sawSelfCallInConstructor;

    private final BugReporter bugReporter;

    private final BugAccumulator bugAccumulator;

    boolean publicOrProtectedConstructor;

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#getReadFields()} instead
     */
    @Deprecated
    public Set<? extends XField> getReadFields() {
        return data.getReadFields();
    }

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#getWrittenFields()} instead
     */
    @Deprecated
    public Set<? extends XField> getWrittenFields() {
        return data.getWrittenFields();
    }

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#isWrittenOutsideOfInitialization(XField)} instead
     */
    @Deprecated
    public boolean isWrittenOutsideOfInitialization(XField f) {
        return data.isWrittenOutsideOfInitialization(f);
    }

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#isWrittenDuringInitialization(XField)} instead
     */
    @Deprecated
    public boolean isWrittenDuringInitialization(XField f) {
        return data.isWrittenDuringInitialization(f);
    }

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#isWrittenInConstructor(XField)} instead
     */
    @Deprecated
    public boolean isWrittenInConstructor(XField f) {
        return data.isWrittenInConstructor(f);
    }

    static final int DO_NOT_CONSIDER = ACC_PUBLIC | ACC_PROTECTED;

    final ClassDescriptor externalizable = DescriptorFactory.createClassDescriptor(java.io.Externalizable.class);

    final  ClassDescriptor serializable = DescriptorFactory.createClassDescriptor(java.io.Serializable.class);

    final ClassDescriptor remote = DescriptorFactory.createClassDescriptor(java.rmi.Remote.class);

    public UnreadFields(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
        this.bugAccumulator = new BugAccumulator(bugReporter);
        AnalysisContext context = AnalysisContext.currentAnalysisContext();
        data.reflectiveFields.add( XFactory.createXField("java.lang.System", "in", "Ljava/io/InputStream;", true));
        data.reflectiveFields.add( XFactory.createXField("java.lang.System", "out", "Ljava/io/PrintStream;", true));
        data.reflectiveFields.add( XFactory.createXField("java.lang.System", "err", "Ljava/io/PrintStream;", true));
        data = context.getUnreadFieldsData();
        context.setUnreadFields(this);
    }

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#strongEvidenceForIntendedSerialization(ClassDescriptor)} instead
     */
    @Deprecated
    public void strongEvidenceForIntendedSerialization(ClassDescriptor c) {
        data.strongEvidenceForIntendedSerialization(c);
    }

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#existsStrongEvidenceForIntendedSerialization(ClassDescriptor)} instead
     */
    @Deprecated
    public boolean existsStrongEvidenceForIntendedSerialization(ClassDescriptor c) {
        return data.existsStrongEvidenceForIntendedSerialization(c);
    }

    @Override
    public void visit(JavaClass obj) {
        data.calledFromConstructors.clear();
        hasNativeMethods = false;
        sawSelfCallInConstructor = false;
        publicOrProtectedConstructor = false;
        isSerializable = false;
        if (obj.isAbstract()) {
            data.abstractClasses.add(getDottedClassName());
        } else {
            String superClass = obj.getSuperclassName();
            if (superClass != null)
                data.hasNonAbstractSubClass.add(superClass);
        }
        data.classesScanned.add(getDottedClassName());
        boolean superClassIsObject = "java.lang.Object".equals(obj.getSuperclassName());
        if (getSuperclassName().indexOf("$") >= 0 || getSuperclassName().indexOf("+") >= 0
                || withinAnonymousClass.matcher(getDottedClassName()).find()) {
            // System.out.println("hicfsc: " + betterClassName);
            data.innerClassCannotBeStatic.add(getDottedClassName());
            // System.out.println("hicfsc: " + betterSuperclassName);
            data.innerClassCannotBeStatic.add(getDottedSuperclassName());
        }
        // Does this class directly implement Serializable?
        String[] interface_names = obj.getInterfaceNames();
        for (String interface_name : interface_names) {
            if (interface_name.equals("java.io.Externalizable")) {
                isSerializable = true;
            } else if (interface_name.equals("java.io.Serializable")) {
                isSerializable = true;
                break;
            }
        }

        // Does this class indirectly implement Serializable?
        if ((!superClassIsObject || interface_names.length > 0) && !isSerializable) {
            try {
                Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2();
                ClassDescriptor desc = DescriptorFactory.createClassDescriptor(obj);
                if (subtypes2.getSubtypes(serializable).contains(desc) || subtypes2.getSubtypes(externalizable).contains(desc)
                        || subtypes2.getSubtypes(remote).contains(desc)) {
                    isSerializable = true;
                }
            } catch (ClassNotFoundException e) {
                bugReporter.reportMissingClass(e);
            }
        }

        // System.out.println(getDottedClassName() + " is serializable: " +
        // isSerializable);
        super.visit(obj);
    }

    public static boolean classHasParameter(JavaClass obj) {
        for (Attribute a : obj.getAttributes())
            if (a instanceof Signature) {
                String sig = ((Signature) a).getSignature();
                return sig.charAt(0) == '<';
            }
        return false;
    }

    @Override
    public void visitAfter(JavaClass obj) {
        if (hasNativeMethods) {
            data.fieldsOfSerializableOrNativeClassed.addAll(data.myFields);
            data.fieldsOfNativeClasses.addAll(data.myFields);
        }
        if (isSerializable) {
            data.fieldsOfSerializableOrNativeClassed.addAll(data.myFields);
        }
        if (sawSelfCallInConstructor) {
            data.myFields.removeAll(data.writtenInConstructorFields);
            data.writtenInInitializationFields.addAll(data.myFields);
        }
        data.myFields.clear();
        data.allMyFields.clear();
        data.calledFromConstructors.clear();
    }

    @Override
    public void visit(Field obj) {
        super.visit(obj);
        XField f = XFactory.createXField(this);
        data.allMyFields.add(f);
        String signature = obj.getSignature();
        if (!getFieldName().equals("serialVersionUID")) {

            data.myFields.add(f);
            if (obj.getName().equals("_jspx_dependants"))
                data.containerFields.add(f);
        }
        if (isSeleniumWebElement(signature))
            data.containerFields.add(f);
    }

    /**
     * @param signature
     * @return
     */
    public static boolean isSeleniumWebElement(String signature) {
        return signature.equals("Lorg/openqa/selenium/RenderedWebElement;")
                || signature.equals("Lorg/openqa/selenium/WebElement;");
    }

    @Override
    public void visitAnnotation(String annotationClass, Map<String, ElementValue> map, boolean runtimeVisible) {
        if (!visitingField())
            return;
        if (isInjectionAttribute(annotationClass)) {
            data.containerFields.add(XFactory.createXField(this));
        }
        if (!annotationClass.startsWith("edu.umd.cs.findbugs") && !annotationClass.startsWith("javax.lang"))
            data.unknownAnnotation.add(XFactory.createXField(this), annotationClass);

    }

    public static boolean isInjectionAttribute(@DottedClassName String annotationClass) {
        if (annotationClass.startsWith("javax.annotation.")
                || annotationClass.startsWith("javax.ejb")
                || annotationClass.equals("org.apache.tapestry5.annotations.Persist")
                || annotationClass.equals("org.jboss.seam.annotations.In")
                || annotationClass.startsWith("javax.persistence")
                || annotationClass.endsWith("SpringBean")
                || annotationClass.equals("com.google.inject.Inject")
                || annotationClass.startsWith("com.google.") && annotationClass.endsWith(".Bind")
                && annotationClass.hashCode() == -243168318
                || annotationClass.startsWith("org.nuxeo.common.xmap.annotation")
                || annotationClass.startsWith("com.google.gwt.uibinder.client")
                || annotationClass.startsWith("org.springframework.beans.factory.annotation")
                || annotationClass.equals("javax.ws.rs.core.Context"))
            return true;
        int lastDot = annotationClass.lastIndexOf('.');
        String lastPart = annotationClass.substring(lastDot + 1);
        if (lastPart.startsWith("Inject"))
            return true;
        return false;
    }

    @Override
    public void visit(ConstantValue obj) {
        // ConstantValue is an attribute of a field, so the instance variables
        // set during visitation of the Field are still valid here
        XField f = XFactory.createXField(this);
        data.constantFields.add(f);
    }

    int count_aload_1;

    private int previousOpcode;

    private int previousPreviousOpcode;

    @Override
    public void visit(Code obj) {

        count_aload_1 = 0;
        previousOpcode = -1;
        previousPreviousOpcode = -1;
        data.nullTested.clear();
        seenInvokeStatic = false;
        seenMonitorEnter = getMethod().isSynchronized();
        data.staticFieldsReadInThisMethod.clear();
        super.visit(obj);
        if (getMethodName().equals("<init>") && count_aload_1 > 1
                && (getClassName().indexOf('$') >= 0 || getClassName().indexOf('+') >= 0)) {
            data.needsOuterObjectInConstructor.add(getDottedClassName());
            // System.out.println(betterClassName +
            // " needs outer object in constructor");
        }
        bugAccumulator.reportAccumulatedBugs();
    }

    @Override
    public void visit(Method obj) {
        if (DEBUG)
            System.out.println("Checking " + getClassName() + "." + obj.getName());
        if (getMethodName().equals("<init>") && (obj.isPublic() || obj.isProtected()))
            publicOrProtectedConstructor = true;
        pendingGetField = null;
        saState = 0;
        super.visit(obj);
        int flags = obj.getAccessFlags();
        if ((flags & ACC_NATIVE) != 0)
            hasNativeMethods = true;
    }

    boolean seenInvokeStatic;

    boolean seenMonitorEnter;

    XField pendingGetField;

    UnreadFieldsData data = new UnreadFieldsData();
    int saState;

    @Override
    public void sawOpcode(int seen) {
        if (DEBUG)
            System.out.println(getPC() + ": " + OPCODE_NAMES[seen] + " " + saState);
        if (seen == MONITORENTER)
            seenMonitorEnter = true;
        switch (saState) {
        case 0:
            if (seen == ALOAD_0)
                saState = 1;
            break;
        case 1:
            if (seen == ALOAD_0)
                saState = 2;
            else
                saState = 0;
            break;
        case 2:
            if (seen == GETFIELD)
                saState = 3;
            else
                saState = 0;
            break;
        case 3:
            if (seen == PUTFIELD)
                saState = 4;
            else
                saState = 0;
            break;
        }
        boolean selfAssignment = false;
        if (pendingGetField != null) {
            if (seen != PUTFIELD && seen != PUTSTATIC)
                data.readFields.add(pendingGetField);
            else if (XFactory.createReferencedXField(this).equals(pendingGetField) && (saState == 4 || seen == PUTSTATIC))
                selfAssignment = true;
            else
                data.readFields.add(pendingGetField);
            pendingGetField = null;
        }
        if (saState == 4)
            saState = 0;

        if (seen == INVOKESTATIC && getClassConstantOperand().equals("java/util/concurrent/atomic/AtomicReferenceFieldUpdater")
                && getNameConstantOperand().equals("newUpdater")) {
            String fieldName = (String) stack.getStackItem(0).getConstant();
            String fieldSignature = (String) stack.getStackItem(1).getConstant();
            String fieldClass = (String) stack.getStackItem(2).getConstant();
            if (fieldName != null && fieldSignature != null && fieldClass != null) {
                XField f = XFactory.createXField(fieldClass.replace('/', '.'), fieldName, ClassName.toSignature(fieldSignature),
                        false);
                data.reflectiveFields.add(f);
            }

        }
        if (seen == INVOKESTATIC && getClassConstantOperand().equals("java/util/concurrent/atomic/AtomicIntegerFieldUpdater")
                && getNameConstantOperand().equals("newUpdater")) {
            String fieldName = (String) stack.getStackItem(0).getConstant();
            String fieldClass = (String) stack.getStackItem(1).getConstant();
            if (fieldName != null && fieldClass != null) {
                XField f = XFactory.createXField(fieldClass.replace('/', '.'), fieldName, "I", false);
                data.reflectiveFields.add(f);
            }

        }
        if (seen == INVOKESTATIC && getClassConstantOperand().equals("java/util/concurrent/atomic/AtomicLongFieldUpdater")
                && getNameConstantOperand().equals("newUpdater")) {
            String fieldName = (String) stack.getStackItem(0).getConstant();
            String fieldClass = (String) stack.getStackItem(1).getConstant();
            if (fieldName != null && fieldClass != null) {
                XField f = XFactory.createXField(fieldClass.replace('/', '.'), fieldName, "J", false);
                data.reflectiveFields.add(f);
            }

        }

        if (seen == GETSTATIC) {
            XField f = XFactory.createReferencedXField(this);
            data.staticFieldsReadInThisMethod.add(f);
        } else if (seen == INVOKESTATIC) {
            seenInvokeStatic = true;
        } else if (seen == PUTSTATIC && !getMethod().isStatic()) {
            XField f = XFactory.createReferencedXField(this);
            OpcodeStack.Item valuePut = getStack().getStackItem(0);

            checkWriteToStaticFromInstanceMethod: if (f.getName().indexOf("class$") != 0) {
                int priority = LOW_PRIORITY;
                if (f.isReferenceType())
                    try {
                        ValueNumberDataflow vnaDataflow = getClassContext().getValueNumberDataflow(getMethod());
                        IsNullValueDataflow invDataflow = getClassContext().getIsNullValueDataflow(getMethod());
                        ValueNumberFrame vFrame = vnaDataflow.getAnalysis().getFactAtPC(vnaDataflow.getCFG(), getPC());
                        IsNullValueFrame iFrame = invDataflow.getAnalysis().getFactAtPC(invDataflow.getCFG(), getPC());
                        AvailableLoad l = new AvailableLoad(f);
                        ValueNumber[] availableLoads = vFrame.getAvailableLoad(l);
                        if (availableLoads != null && iFrame.isTrackValueNumbers())
                            for (ValueNumber v : availableLoads) {
                                IsNullValue knownValue = iFrame.getKnownValue(v);
                                if (knownValue == null)
                                    continue;
                                if (knownValue.isDefinitelyNotNull()) {
                                    if (valuePut.isNull())
                                        priority++;
                                    else
                                        priority--;
                                    break;
                                } else if (knownValue.isDefinitelyNull()) {
                                    break checkWriteToStaticFromInstanceMethod;
                                }
                            }

                    } catch (CheckedAnalysisException e) {
                        AnalysisContext.logError("foo", e);
                    }

                if (!publicOrProtectedConstructor) {
                    priority--;
                }
                if (seenMonitorEnter)
                    priority++;
                if (!seenInvokeStatic && data.staticFieldsReadInThisMethod.isEmpty())
                    priority--;
                if (getThisClass().isPublic() && getMethod().isPublic())
                    priority--;
                if (getThisClass().isPrivate() || getMethod().isPrivate())
                    priority++;
                if (getClassName().indexOf('$') != -1 || getMethod().isSynthetic() || f.isSynthetic()
                        || f.getName().indexOf('$') >= 0)
                    priority++;

                // Decrease priority for boolean fields used to control
                // debug/test settings
                if (f.getName().indexOf("DEBUG") >= 0 || f.getName().indexOf("VERBOSE") >= 0 && f.getSignature().equals("Z")) {
                    priority++;
                    priority++;
                }
                // Eclipse bundles which implements start/stop *very* often
                // assigns static instances there
                if ((getMethodName().equals("start") || getMethodName().equals("stop"))
                        && getMethodSig().equals("(Lorg/osgi/framework/BundleContext;)V")) {
                    try {
                        JavaClass bundleClass = Repository.lookupClass("org.osgi.framework.BundleActivator");
                        if (getThisClass().instanceOf(bundleClass)) {
                            priority++;
                        }
                        if (f.isReferenceType()) {
                            FieldDescriptor fieldInfo = f.getFieldDescriptor();
                            String dottedClass = DeepSubtypeAnalysis.getComponentClass(fieldInfo.getSignature());
                            JavaClass fieldClass = Repository.lookupClass(dottedClass);
                            if (fieldClass != null && fieldClass.instanceOf(bundleClass)) {
                                // the code "plugin = this;" unfortunately
                                // exists in the
                                // template for new Eclipse plugin classes, so
                                // nearly every one
                                // plugin has this pattern => decrease to very
                                // low prio
                                priority = Priorities.IGNORE_PRIORITY;
                            }
                        }
                    } catch (ClassNotFoundException e) {
                        bugReporter.reportMissingClass(e);
                    }
                }
                bugAccumulator.accumulateBug(new BugInstance(this, "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", priority)
                        .addClassAndMethod(this).addField(f), this);

            }
        }

        if (seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE || seen == INVOKESPECIAL || seen == INVOKESTATIC) {

            String sig = getSigConstantOperand();
            String invokedClassName = getClassConstantOperand();
            if (invokedClassName.equals(getClassName())
                    && (getMethodName().equals("<init>") || getMethodName().equals("<clinit>"))) {

                data.calledFromConstructors.add(getNameConstantOperand() + ":" + sig);
            }
            int pos = PreorderVisitor.getNumberArguments(sig);
            if (stack.getStackDepth() > pos) {
                OpcodeStack.Item item = stack.getStackItem(pos);
                boolean superCall = seen == INVOKESPECIAL && !invokedClassName.equals(getClassName());

                if (DEBUG)
                    System.out.println("In " + getFullyQualifiedMethodName() + " saw call on " + item);

                boolean selfCall = item.getRegisterNumber() == 0 && !superCall;
                if (selfCall && getMethodName().equals("<init>")) {
                    sawSelfCallInConstructor = true;
                    if (DEBUG)
                        System.out.println("Saw self call in " + getFullyQualifiedMethodName() + " to " + invokedClassName + "."
                                + getNameConstantOperand());
                }
            }
        }

        if ((seen == IFNULL || seen == IFNONNULL) && stack.getStackDepth() > 0) {
            OpcodeStack.Item item = stack.getStackItem(0);
            XField f = item.getXField();
            if (f != null) {
                data.nullTested.add(f);
                if (DEBUG)
                    System.out.println(f + " null checked in " + getFullyQualifiedMethodName());
            }
        }

        if ((seen == IF_ACMPEQ || seen == IF_ACMPNE) && stack.getStackDepth() >= 2) {
            OpcodeStack.Item item0 = stack.getStackItem(0);
            OpcodeStack.Item item1 = stack.getStackItem(1);
            XField field1 = item1.getXField();
            if (item0.isNull() && field1 != null)
                data.nullTested.add(field1);
            else {
                XField field0 = item0.getXField();
                if (item1.isNull() && field0 != null)
                    data.nullTested.add(field0);
            }
        }

        computePlacesAssumedNonnull: if (seen == GETFIELD || seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE
                || seen == INVOKESPECIAL || seen == PUTFIELD || seen == IALOAD || seen == AALOAD || seen == BALOAD
                || seen == CALOAD || seen == SALOAD || seen == IASTORE || seen == AASTORE || seen == BASTORE || seen == CASTORE
                || seen == SASTORE || seen == ARRAYLENGTH) {
            int pos = 0;
            switch (seen) {
            case ARRAYLENGTH:
            case GETFIELD:
                pos = 0;
                break;
            case INVOKEVIRTUAL:
            case INVOKEINTERFACE:
            case INVOKESPECIAL:
                String sig = getSigConstantOperand();
                pos = PreorderVisitor.getNumberArguments(sig);
                break;
            case PUTFIELD:
            case IALOAD:
            case AALOAD:
            case BALOAD:
            case CALOAD:
            case SALOAD:
                pos = 1;
                break;
            case IASTORE:
            case AASTORE:
            case BASTORE:
            case CASTORE:
            case SASTORE:
                pos = 2;
                break;
            default:
                throw new RuntimeException("Impossible");
            }
            if (stack.getStackDepth() >= pos) {
                OpcodeStack.Item item = stack.getStackItem(pos);
                XField f = item.getXField();
                if (f != null
                        && !f.isStatic()
                        && !data.nullTested.contains(f)
                        && !((data.writtenInConstructorFields.contains(f) || data.writtenInInitializationFields.contains(f)) && data.writtenNonNullFields
                                .contains(f))) {
                    try {
                        IsNullValueDataflow invDataflow = getClassContext().getIsNullValueDataflow(getMethod());
                        IsNullValueFrame iFrame = invDataflow.getAnalysis().getFactBeforeExceptionCheck(invDataflow.getCFG(),
                                getPC());
                        if (!iFrame.isValid() || iFrame.getStackValue(pos).isDefinitelyNotNull())
                            break computePlacesAssumedNonnull;

                    } catch (CheckedAnalysisException e) {
                        AnalysisContext.logError("foo", e);
                    }
                    if (DEBUG)
                        System.out.println("RRR: " + f + " " + data.nullTested.contains(f) + " "
                                + data.writtenInConstructorFields.contains(f) + " " + data.writtenNonNullFields.contains(f));

                    ProgramPoint p = new ProgramPoint(this);
                    Set<ProgramPoint> s = data.assumedNonNull.get(f);
                    if (s == null)
                        s = Collections.singleton(p);
                    else
                        s = Util.addTo(s, p);
                    data.assumedNonNull.put(f, s);
                    if (DEBUG)
                        System.out.println(f + " assumed non-null in " + getFullyQualifiedMethodName());
                }
            }
        }

        if (seen == ALOAD_1) {
            count_aload_1++;
        } else if (seen == GETFIELD || seen == GETSTATIC) {
            XField f = XFactory.createReferencedXField(this);
            pendingGetField = f;
            if (getMethodName().equals("readResolve") && seen == GETFIELD) {
                data.writtenFields.add(f);
                data.writtenNonNullFields.add(f);
            }
            if (DEBUG)
                System.out.println("get: " + f);
            if (data.writtenFields.contains(f))
                data.fieldAccess.remove(f);
            else if (!data.fieldAccess.containsKey(f))
                data.fieldAccess.put(f, SourceLineAnnotation.fromVisitedInstruction(this));
        } else if ((seen == PUTFIELD || seen == PUTSTATIC) && !selfAssignment) {
            XField f = XFactory.createReferencedXField(this);
            OpcodeStack.Item item = null;
            if (stack.getStackDepth() > 0) {
                item = stack.getStackItem(0);
                if (!item.isNull())
                    data.nullTested.add(f);
            }
            data.writtenFields.add(f);

            boolean writtingNonNull = previousOpcode != ACONST_NULL || previousPreviousOpcode == GOTO;
            if (writtingNonNull) {
                data.writtenNonNullFields.add(f);
                if (DEBUG)
                    System.out.println("put nn: " + f);
            } else if (DEBUG)
                System.out.println("put: " + f);
            if (writtingNonNull && data.readFields.contains(f))
                data.fieldAccess.remove(f);
            else if (!data.fieldAccess.containsKey(f))
                data.fieldAccess.put(f, SourceLineAnnotation.fromVisitedInstruction(this));

            boolean isConstructor = getMethodName().equals("<init>") || getMethodName().equals("<clinit>");
            if (getMethod().isStatic() == f.isStatic()
                    && (isConstructor || data.calledFromConstructors.contains(getMethodName() + ":" + getMethodSig())
                            || getMethodName().equals("init") || getMethodName().equals("init")
                            || getMethodName().equals("initialize") || getMethod().isPrivate())) {

                if (isConstructor) {
                    data.writtenInConstructorFields.add(f);
                    if (f.getSignature().equals("Ljava/lang/ThreadLocal;") && item != null && item.isNewlyAllocated())
                        data.threadLocalAssignedInConstructor.put(f, new ProgramPoint(this));
                } else
                    data.writtenInInitializationFields.add(f);
                if (writtingNonNull)
                    data.assumedNonNull.remove(f);
            } else {
                data.writtenOutsideOfInitializationFields.add(f);
            }

        }
        previousPreviousOpcode = previousOpcode;
        previousOpcode = seen;
    }

    /**
     * @deprecated Use {@link edu.umd.cs.findbugs.detect.UnreadFieldsData#isReflexive(XField)} instead
     */
    @Deprecated
    public boolean isReflexive(XField f) {
        return data.isReflexive(f);
    }

    static Pattern dontComplainAbout = Pattern.compile("class[$]");

    static Pattern withinAnonymousClass = Pattern.compile("[$][0-9].*[$]");

    @Override
    public void report() {
        Set<String> fieldNamesSet = new HashSet<String>();
        for (XField f : data.writtenNonNullFields)
            fieldNamesSet.add(f.getName());
        if (DEBUG) {
            System.out.println("read fields:");
            for (XField f : data.readFields)
                System.out.println("  " + f);
            if (!data.containerFields.isEmpty()) {
                System.out.println("ejb3 fields:");
                for (XField f : data.containerFields)
                    System.out.println("  " + f);
            }
            if (!data.reflectiveFields.isEmpty()) {
                System.out.println("reflective fields:");
                for (XField f : data.reflectiveFields)
                    System.out.println("  " + f);
            }

            System.out.println("written fields:");
            for (XField f : data.writtenFields)
                System.out.println("  " + f);
            System.out.println("written nonnull fields:");
            for (XField f : data.writtenNonNullFields)
                System.out.println("  " + f);

            System.out.println("assumed nonnull fields:");
            for (XField f : data.assumedNonNull.keySet())
                System.out.println("  " + f);
        }
        Set<XField> declaredFields = new HashSet<XField>();
        AnalysisContext currentAnalysisContext = AnalysisContext.currentAnalysisContext();
        XFactory xFactory = AnalysisContext.currentXFactory();
        for (XField f : AnalysisContext.currentXFactory().allFields()) {
            ClassDescriptor classDescriptor = f.getClassDescriptor();
            if (currentAnalysisContext.isApplicationClass(classDescriptor) && !currentAnalysisContext.isTooBig(classDescriptor)
                    && !xFactory.isReflectiveClass(classDescriptor))
                declaredFields.add(f);
        }
        // Don't report anything about ejb3Fields
        HashSet<XField> unknownAnotationAndUnwritten = new HashSet<XField>(data.unknownAnnotation.keySet());
        unknownAnotationAndUnwritten.removeAll(data.writtenFields);
        declaredFields.removeAll(unknownAnotationAndUnwritten);
        declaredFields.removeAll(data.containerFields);
        declaredFields.removeAll(data.reflectiveFields);
        for (Iterator<XField> i = declaredFields.iterator(); i.hasNext();) {
            XField f = i.next();
            if (f.isSynthetic() && !f.getName().startsWith("this$") || f.getName().startsWith("_"))
                i.remove();
        }

        TreeSet<XField> notInitializedInConstructors = new TreeSet<XField>(declaredFields);
        notInitializedInConstructors.retainAll(data.readFields);
        notInitializedInConstructors.retainAll(data.writtenNonNullFields);
        notInitializedInConstructors.retainAll(data.assumedNonNull.keySet());
        notInitializedInConstructors.removeAll(data.writtenInConstructorFields);
        notInitializedInConstructors.removeAll(data.writtenInInitializationFields);

        for (Iterator<XField> i = notInitializedInConstructors.iterator(); i.hasNext();) {
            if (i.next().isStatic())
                i.remove();
        }

        TreeSet<XField> readOnlyFields = new TreeSet<XField>(declaredFields);
        readOnlyFields.removeAll(data.writtenFields);

        readOnlyFields.retainAll(data.readFields);

        TreeSet<XField> nullOnlyFields = new TreeSet<XField>(declaredFields);
        nullOnlyFields.removeAll(data.writtenNonNullFields);

        nullOnlyFields.retainAll(data.readFields);

        Set<XField> writeOnlyFields = declaredFields;
        writeOnlyFields.removeAll(data.readFields);

        Map<String, Integer> count = new HashMap<String, Integer>();
        Bag<String> nullOnlyFieldNames = new Bag<String>();
        Bag<ClassDescriptor> classContainingNullOnlyFields = new Bag<ClassDescriptor>();

        for (XField f : nullOnlyFields) {
            nullOnlyFieldNames.add(f.getName());
            classContainingNullOnlyFields.add(f.getClassDescriptor());
            int increment = 3;
            Collection<ProgramPoint> assumedNonNullAt = data.assumedNonNull.get(f);
            if (assumedNonNullAt != null)
                increment += assumedNonNullAt.size();
            for (String s : data.unknownAnnotation.get(f)) {
                Integer value = count.get(s);
                if (value == null)
                    count.put(s, increment);
                else
                    count.put(s, value + increment);
            }
        }
        Map<XField, Integer> maxCount = new HashMap<XField, Integer>();

        LinkedList<XField> assumeReflective = new LinkedList<XField>();
        for (XField f : nullOnlyFields) {
            int myMaxCount = 0;
            for (String s : data.unknownAnnotation.get(f)) {
                Integer value = count.get(s);
                if (value != null && myMaxCount < value)
                    myMaxCount = value;
            }
            if (myMaxCount > 0)
                maxCount.put(f, myMaxCount);
            if (myMaxCount > 15)
                assumeReflective.add(f);
            else if (nullOnlyFieldNames.getCount(f.getName()) > 8)
                assumeReflective.add(f);
            else if (classContainingNullOnlyFields.getCount(f.getClassDescriptor()) > 4)
                assumeReflective.add(f);
            else if (classContainingNullOnlyFields.getCount(f.getClassDescriptor()) > 2 && f.getName().length() == 1)
                assumeReflective.add(f);

        }

        readOnlyFields.removeAll(assumeReflective);
        nullOnlyFields.removeAll(assumeReflective);
        notInitializedInConstructors.removeAll(assumeReflective);

        Bag<String> notInitializedUses = new Bag<String>();
        for (XField f : notInitializedInConstructors) {
            String className = f.getClassName();
            Set<ProgramPoint> assumedNonnullAt = data.assumedNonNull.get(f);
            notInitializedUses.add(className, assumedNonnullAt.size());
        }
        for (XField f : notInitializedInConstructors) {
            String className = f.getClassName();
            if (notInitializedUses.getCount(className) >= 8)
                continue;
            String fieldSignature = f.getSignature();
            if (f.isResolved() && !data.fieldsOfNativeClasses.contains(f)
                    && (fieldSignature.charAt(0) == 'L' || fieldSignature.charAt(0) == '[')) {
                int priority = LOW_PRIORITY;

                Set<ProgramPoint> assumedNonnullAt = data.assumedNonNull.get(f);
                if (assumedNonnullAt.size() < 4)
                    for (ProgramPoint p : assumedNonnullAt) {
                        BugInstance bug = new BugInstance(this, "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", priority)
                                .addClass(className).addField(f).addMethod(p.getMethodAnnotation());
                        bugAccumulator.accumulateBug(bug, p.getSourceLineAnnotation());
                    }

            }
        }

        for (XField f : readOnlyFields) {
            String fieldName = f.getName();
            String className = f.getClassName();
            String fieldSignature = f.getSignature();
            if (f.isResolved() && !data.fieldsOfNativeClasses.contains(f)) {
                int priority = NORMAL_PRIORITY;
                if (!(fieldSignature.charAt(0) == 'L' || fieldSignature.charAt(0) == '['))
                    priority++;
                if (maxCount.containsKey(f))
                    priority++;
                String pattern = "UWF_UNWRITTEN_FIELD";
                if (f.isProtected() || f.isPublic())
                    pattern = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD";
                bugReporter.reportBug(addClassFieldAndAccess(new BugInstance(this, pattern, priority), f));
            }

        }
        for (XField f : nullOnlyFields) {
            String fieldName = f.getName();
            String className = f.getClassName();
            String fieldSignature = f.getSignature();
            if (DEBUG) {
                System.out.println("Null only: " + f);
                System.out.println("   : " + data.assumedNonNull.containsKey(f));
                System.out.println("   : " + data.fieldsOfSerializableOrNativeClassed.contains(f));
                System.out.println("   : " + fieldNamesSet.contains(f.getName()));
                System.out.println("   : " + data.abstractClasses.contains(f.getClassName()));
                System.out.println("   : " + data.hasNonAbstractSubClass.contains(f.getClassName()));
                System.out.println("   : " + f.isResolved());
            }
            if (!f.isResolved())
                continue;
            if (data.fieldsOfNativeClasses.contains(f))
                continue;
            if (DEBUG) {
                System.out.println("Ready to report");
            }
            int priority = NORMAL_PRIORITY;
            if (maxCount.containsKey(f))
                priority++;
            if (data.abstractClasses.contains(f.getClassName())) {
                priority++;
                if (!data.hasNonAbstractSubClass.contains(f.getClassName()))
                    priority++;
            }
            // if (fieldNamesSet.contains(f.getName())) priority++;
            if (data.assumedNonNull.containsKey(f)) {
                int npPriority = priority;

                Set<ProgramPoint> assumedNonNullAt = data.assumedNonNull.get(f);
                if (assumedNonNullAt.size() > 14) {
                    npPriority += 2;
                } else if (assumedNonNullAt.size() > 6) {
                    npPriority++;
                } else {
                    priority--;
                }
                String pattern = (f.isPublic() || f.isProtected()) ? "NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD"
                        : "NP_UNWRITTEN_FIELD";
                for (ProgramPoint p : assumedNonNullAt)
                    bugAccumulator.accumulateBug(
                            new BugInstance(this, pattern, npPriority).addClassAndMethod(p.method).addField(f),
                            p.getSourceLineAnnotation());

            } else {
                if (f.isStatic())
                    priority++;
                if (f.isFinal())
                    priority++;
                if (data.fieldsOfSerializableOrNativeClassed.contains(f))
                    priority++;
            }
            if (!readOnlyFields.contains(f))
                bugReporter.reportBug(addClassFieldAndAccess(new BugInstance(this, "UWF_NULL_FIELD", priority), f)
                        .lowerPriorityIfDeprecated());
        }

        writeOnlyFields: for (XField f : writeOnlyFields) {
            String fieldName = f.getName();
            String className = f.getClassName();
            int lastDollar = Math.max(className.lastIndexOf('$'), className.lastIndexOf('+'));
            boolean isAnonymousInnerClass = (lastDollar > 0) && (lastDollar < className.length() - 1)
                    && Character.isDigit(className.charAt(lastDollar + 1));

            if (DEBUG) {
                System.out.println("Checking write only field " + className + "." + fieldName + "\t" + data.constantFields.contains(f)
                        + "\t" + f.isStatic());
            }
            if (!f.isResolved())
                continue;
            if (dontComplainAbout.matcher(fieldName).find())
                continue;
            if (lastDollar >= 0 && (fieldName.startsWith("this$") || fieldName.startsWith("this+"))) {
                String outerClassName = className.substring(0, lastDollar);

                try {
                    XClass thisClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, f.getClassDescriptor());

                    if (isAnonymousInnerClass)
                        for (XField f2 : thisClass.getXFields()) {
                            if (f2 != f && f2.isPrivate() && f2.isSynthetic() && !f2.getName().startsWith("this$")
                                    && f2.getName().contains("$"))
                                continue writeOnlyFields;
                        }
                    JavaClass outerClass = Repository.lookupClass(outerClassName);
                    if (classHasParameter(outerClass))
                        continue;

                    ClassDescriptor cDesc = DescriptorFactory.createClassDescriptorFromDottedClassName(outerClassName);

                    XClass outerXClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, cDesc);

                    AnalysisContext analysisContext = AnalysisContext.currentAnalysisContext();

                    Subtypes2 subtypes2 = analysisContext.getSubtypes2();

                    for (XField of : outerXClass.getXFields())
                        if (!of.isStatic()) {
                            String sourceSignature = of.getSourceSignature();
                            if (sourceSignature != null && of.getSignature().equals("Ljava/lang/ThreadLocal;")) {
                                Type ofType = GenericUtilities.getType(sourceSignature);
                                if (ofType instanceof GenericObjectType) {
                                    GenericObjectType gType = (GenericObjectType) ofType;

                                    for (ReferenceType r : gType.getParameters()) {
                                        if (r instanceof ObjectType) {
                                            ClassDescriptor c = DescriptorFactory.getClassDescriptor((ObjectType) r);
                                            if (subtypes2.isSubtype(f.getClassDescriptor(), c)) {
                                                ProgramPoint p = data.threadLocalAssignedInConstructor.get(of);
                                                int priority = p == null ? NORMAL_PRIORITY : HIGH_PRIORITY;
                                                BugInstance bug = new BugInstance(this, "SIC_THREADLOCAL_DEADLY_EMBRACE",
                                                        priority).addClass(className).addField(of);
                                                if (p != null)
                                                    bug.addMethod(p.method).add(p.getSourceLineAnnotation());
                                                bugReporter.reportBug(bug);
                                            }
                                        }
                                    }
                                }
                            }

                        }

                    boolean outerClassIsInnerClass = false;
                    for (Field field : outerClass.getFields())
                        if (field.getName().equals("this$0"))
                            outerClassIsInnerClass = true;
                    if (outerClassIsInnerClass)
                        continue;
                } catch (ClassNotFoundException e) {
                    bugReporter.reportMissingClass(e);
                } catch (CheckedAnalysisException e) {
                    bugReporter.logError("Error getting outer XClass for " + outerClassName, e);
                }
                if (!data.innerClassCannotBeStatic.contains(className)) {
                    boolean easyChange = !data.needsOuterObjectInConstructor.contains(className);
                    if (easyChange || !isAnonymousInnerClass) {

                        // easyChange isAnonymousInnerClass
                        // true false medium, SIC
                        // true true low, SIC_ANON
                        // false true not reported
                        // false false low, SIC_THIS
                        int priority = LOW_PRIORITY;
                        if (easyChange && !isAnonymousInnerClass)
                            priority = NORMAL_PRIORITY;

                        String bug = "SIC_INNER_SHOULD_BE_STATIC";
                        if (isAnonymousInnerClass)
                            bug = "SIC_INNER_SHOULD_BE_STATIC_ANON";
                        else if (!easyChange)
                            bug = "SIC_INNER_SHOULD_BE_STATIC_NEEDS_THIS";

                        bugReporter.reportBug(new BugInstance(this, bug, priority).addClass(className));

                    }
                }
            } else if (f.isResolved()) {
                if (data.constantFields.contains(f)) {
                    if (!f.isStatic())
                        bugReporter.reportBug(addClassFieldAndAccess(
                                new BugInstance(this, "SS_SHOULD_BE_STATIC", NORMAL_PRIORITY), f));
                } else if (data.fieldsOfSerializableOrNativeClassed.contains(f)) {
                    // ignore it
                } else if (!data.writtenFields.contains(f))
                    bugReporter.reportBug(new BugInstance(this,
                            (f.isPublic() || f.isProtected()) ? "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD" : "UUF_UNUSED_FIELD",
                            NORMAL_PRIORITY).addClass(className).addField(f).lowerPriorityIfDeprecated());
                else if (f.getName().toLowerCase().indexOf("guardian") < 0) {
                    int priority = NORMAL_PRIORITY;
                    if (f.isStatic())
                        priority++;
                    if (f.isFinal())
                        priority++;
                    bugReporter.reportBug(addClassFieldAndAccess(new BugInstance(this,
                            (f.isPublic() || f.isProtected()) ? "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD" : "URF_UNREAD_FIELD",
                            priority), f));
                }
            }
        }
        bugAccumulator.reportAccumulatedBugs();
    }

    /**
     * @param instance
     * @return
     */
    private BugInstance addClassFieldAndAccess(BugInstance instance, XField f) {
        if (data.writtenNonNullFields.contains(f) && data.readFields.contains(f))
            throw new IllegalArgumentException("No information for fields that are both read and written nonnull");

        instance.addClass(f.getClassName()).addField(f);
        if (data.fieldAccess.containsKey(f))
            instance.add(data.fieldAccess.get(f));
        return instance;
    }

}
